diff --git a/Project.xml b/Project.xml index 8c133047d..8208dc649 100644 --- a/Project.xml +++ b/Project.xml @@ -29,9 +29,6 @@ - - - @@ -40,12 +37,9 @@ - - - @@ -102,9 +96,8 @@ - + - @@ -223,6 +216,28 @@ + +
+ + + + + + + + + + + + + + + + + + +
+
diff --git a/README.md b/README.md index 4d209a9b5..d868c6574 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,26 @@ This is the repository for Friday Night Funkin, a game originally made for Ludum Dare 47 "Stuck In a Loop". -Play the Ludum Dare prototype here: https://ninja-muffin24.itch.io/friday-night-funkin -Play the Newgrounds one here: https://www.newgrounds.com/portal/view/770371 -Support the project on the itch.io page: https://ninja-muffin24.itch.io/funkin +Play free in your browser on Newgrounds: https://www.newgrounds.com/portal/view/770371 +Download the game for desktop on Itch.io: https://ninja-muffin24.itch.io/funkin + +# Credits + +- [ninjamuffin99](https://twitter.com/ninja_muffin99) - Programmer +- [PhantomArcade3K](https://twitter.com/phantomarcade3k) and [Evilsk8r](https://twitter.com/evilsk8r) - Art +- [Kawaisprite](https://twitter.com/kawaisprite) - Musician + +This game was made with love to Newgrounds and it's community. Extra love to Tom Fulp. + + IF YOU MAKE A MOD AND DISTRIBUTE A MODIFIED / RECOMIPLED VERSION, YOU MUST OPEN SOURCE YOUR MOD AS WELL ## Credits / shoutouts -- [ninjamuffin99 (me!)](https://twitter.com/ninja_muffin99) - Programmer -- [PhantomArcade3K](https://twitter.com/phantomarcade3k) and [Evilsk8r](https://twitter.com/evilsk8r) - Art -- [Kawaisprite](https://twitter.com/kawaisprite) - Musician -This game was made with love to Newgrounds and it's community. Extra love to Tom Fulp. + + ## Build instructions diff --git a/example_mods/.gitignore b/example_mods/.gitignore new file mode 100644 index 000000000..cd193dac6 --- /dev/null +++ b/example_mods/.gitignore @@ -0,0 +1,2 @@ +./tricky +./enaSkin \ No newline at end of file diff --git a/example_mods/modList.txt b/example_mods/modList.txt deleted file mode 100644 index 3e2492595..000000000 --- a/example_mods/modList.txt +++ /dev/null @@ -1 +0,0 @@ -introMod \ No newline at end of file diff --git a/example_mods/readme.txt b/example_mods/readme.txt deleted file mode 100644 index f8d8f6e7e..000000000 --- a/example_mods/readme.txt +++ /dev/null @@ -1,2 +0,0 @@ -THIS MOD FOLDER DOES NOT ENTIRELY WORK JUST YET!!! -DONT EXPECT MUCH OUT OF IT RIGHT NOW!!! \ No newline at end of file diff --git a/example_mods/testing123/_polymod_icon.png b/example_mods/testing123/_polymod_icon.png new file mode 100644 index 000000000..2828bd554 Binary files /dev/null and b/example_mods/testing123/_polymod_icon.png differ diff --git a/example_mods/testing123/_polymod_meta.json b/example_mods/testing123/_polymod_meta.json new file mode 100644 index 000000000..e74efc4f3 --- /dev/null +++ b/example_mods/testing123/_polymod_meta.json @@ -0,0 +1,8 @@ +{ + "title": "Testing123", + "description": "Newgrounds? More like OLDGROUNDS lol.", + "author": "MasterEric", + "api_version": "0.1.0", + "mod_version": "1.0.0", + "license": "Apache-2.0" +} diff --git a/source/Main.hx b/source/Main.hx index 7b4429484..0c219844c 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -1,5 +1,6 @@ package; +import modding.PolymodHandler; import flixel.FlxGame; import flixel.FlxState; import flixel.util.FlxColor; @@ -41,30 +42,14 @@ class Main extends Sprite { super(); - #if polymod - polymod.Polymod.init({ - modRoot: "mods", - dirs: ['testing123'], - frameworkParams: { - assetLibraryPaths: [ - "songs" => "songs", "shared" => "shared", "tutorial" => "tutorial", "week1" => "week1", "week2" => "week2", "week3" => "week3", - "week4" => "week4", "week5" => "week5", "week6" => "week6", "week7" => "week7", "week8" => "week8" - ] - }, - framework: OPENFL, - errorCallback: function(error:polymod.Polymod.PolymodError) - { - trace("POLYMOD ERROR! code = " - + error.code - + " severity = " - + error.severity - + " origin = " - + error.origin - + " message = " - + error.message); - } - }); - #end + // TODO: Ideally this should change to utilize a user interface. + // 1. Call PolymodHandler.getAllMods(). This gives you an array of ModMetadata items, + // each of which contains information about the mod including an icon. + // 2. Provide an interface to enable, disable, and reorder enabled mods. + // A design similar to that of Minecraft resource packs would be intuitive. + // 3. The interface should save (to the save file) and output an ordered array of mod IDs. + // 4. Replace the call to PolymodHandler.loadAllMods() with a call to PolymodHandler.loadModsById(ids:Array). + PolymodHandler.loadAllMods(); if (stage != null) { diff --git a/source/TitleState.hx b/source/TitleState.hx index 084f7e3c5..e5271ed6a 100644 --- a/source/TitleState.hx +++ b/source/TitleState.hx @@ -278,7 +278,7 @@ class TitleState extends MusicBeatState override function update(elapsed:Float) { - trace(FlxG.renderBlit); + // trace(FlxG.renderBlit); #if HAS_PITCH if (FlxG.keys.pressed.UP) diff --git a/source/modding/IHook.hx b/source/modding/IHook.hx new file mode 100644 index 000000000..a1a75d08d --- /dev/null +++ b/source/modding/IHook.hx @@ -0,0 +1,13 @@ +package modding; + +import polymod.hscript.HScriptable; + +/** + * Add this interface to a class to make it a scriptable object. + * Functions annotated with @:hscript will call the relevant script. + */ +@:hscript({ + // ALL of these values are added to ALL scripts in the child classes. + context: [FlxG, FlxSprite, Math, Paths, Std] +}) +interface IHook extends HScriptable {} diff --git a/source/modding/PolymodHandler.hx b/source/modding/PolymodHandler.hx new file mode 100644 index 000000000..3d3c9bace --- /dev/null +++ b/source/modding/PolymodHandler.hx @@ -0,0 +1,201 @@ +package modding; + +#if cpp +import polymod.Polymod; +import polymod.backends.OpenFLBackend; +import polymod.backends.PolymodAssets.PolymodAssetType; +import polymod.format.ParseRules.LinesParseFormat; +import polymod.format.ParseRules.TextFileFormat; +#end + +class PolymodHandler +{ + /** + * The API version that mods should comply with. + * Format this with Semantic Versioning; ... + * Bug fixes increment the patch version, new features increment the minor version. + * Changes that break old mods increment the major version. + */ + static final API_VERSION = "0.1.0"; + + /** + * Where relative to the executable that mods are located. + */ + static final MOD_FOLDER = "mods"; + + /** + * Loads the game with ALL mods enabled with Polymod. + */ + public static function loadAllMods() + { + #if cpp + trace("Initializing Polymod (using all mods)..."); + loadModsById(getAllModIds()); + #else + trace("Polymod not initialized; not supported on this platform."); + #end + } + + /** + * Loads the game without any mods enabled with Polymod. + */ + public static function loadNoMods() + { + // We still need to configure the debug print calls etc. + #if cpp + trace("Initializing Polymod (using no mods)..."); + loadModsById([]); + #else + trace("Polymod not initialized; not supported on this platform."); + #end + } + + public static function loadModsById(ids:Array) + { + #if cpp + if (ids.length == 0) + { + trace('You attempted to load zero mods.'); + } + else + { + trace('Attempting to load ${ids.length} mods...'); + } + var loadedModList = polymod.Polymod.init({ + // Root directory for all mods. + modRoot: MOD_FOLDER, + // The directories for one or more mods to load. + dirs: ids, + // Framework being used to load assets. + framework: OPENFL, + // The current version of our API. + apiVersion: API_VERSION, + // Call this function any time an error occurs. + errorCallback: onPolymodError, + // Enforce semantic version patterns for each mod. + // modVersions: null, + // A map telling Polymod what the asset type is for unfamiliar file extensions. + // extensionMap: [], + + frameworkParams: buildFrameworkParams(), + + // List of filenames to ignore in mods. Use the default list to ignore the metadata file, etc. + ignoredFiles: Polymod.getDefaultIgnoreList(), + + // Parsing rules for various data formats. + parseRules: buildParseRules(), + }); + + if (loadedModList == null) + { + trace('[POLYMOD] An error occurred! Failed when loading mods!'); + } + else + { + if (loadedModList.length == 0) + { + trace('[POLYMOD] Mod loading complete. We loaded no mods / ${ids.length} mods.'); + } + else + { + trace('[POLYMOD] Mod loading complete. We loaded ${loadedModList.length} / ${ids.length} mods.'); + } + } + + for (mod in loadedModList) + trace(' * ${mod.title} v${mod.modVersion} [${mod.id}]'); + + #if debug + var fileList = Polymod.listModFiles("IMAGE"); + trace('[POLYMOD] Installed mods have replaced ${fileList.length} images.'); + for (item in fileList) + trace(' * $item'); + + fileList = Polymod.listModFiles("TEXT"); + trace('[POLYMOD] Installed mods have replaced ${fileList.length} text files.'); + for (item in fileList) + trace(' * $item'); + + fileList = Polymod.listModFiles("MUSIC"); + trace('[POLYMOD] Installed mods have replaced ${fileList.length} music files.'); + for (item in fileList) + trace(' * $item'); + + fileList = Polymod.listModFiles("SOUND"); + trace('[POLYMOD] Installed mods have replaced ${fileList.length} sound files.'); + for (item in fileList) + trace(' * $item'); + #end + #else + trace("[POLYMOD] Mods are not supported on this platform."); + #end + } + + #if cpp + static function buildParseRules():polymod.format.ParseRules + { + var output = polymod.format.ParseRules.getDefault(); + // Ensure TXT files have merge support. + output.addType("txt", TextFileFormat.LINES); + // Ensure script files have merge support. + output.addType("hscript", TextFileFormat.PLAINTEXT); + + // You can specify the format of a specific file, with file extension. + // output.addFile("data/introText.txt", TextFileFormat.LINES) + return output; + } + + static inline function buildFrameworkParams():polymod.Polymod.FrameworkParams + { + return { + assetLibraryPaths: [ + "songs" => "./songs", "shared" => "./", "tutorial" => "./tutorial", "scripts" => "./scripts", "week1" => "./week1", "week2" => "./week2", + "week3" => "./week3", "week4" => "./week4", "week5" => "./week5", "week6" => "./week6", "week7" => "./week7", "week8" => "./week8", + ] + } + } + + static function onPolymodError(error:PolymodError):Void + { + // Perform an action based on the error code. + switch (error.code) + { + case MOD_LOAD_PREPARE: + trace('[POLYMOD] ${error.message}'); + case MOD_LOAD_DONE: + trace('[POLYMOD] ${error.message}'); + case MISSING_ICON: + trace('[POLYMOD] A mod is missing an icon. Please add one.'); + default: + // Log the message based on its severity. + switch (error.severity) + { + case NOTICE: + trace('[POLYMOD] ${error.message}'); + case WARNING: + trace('[POLYMOD] ${error.message}'); + case ERROR: + trace('[POLYMOD] ${error.message}'); + } + } + } + #end + + public static function getAllMods():Array + { + #if cpp + trace('Scanning the mods folder...'); + var modMetadata = Polymod.scan(MOD_FOLDER); + trace('Found ${modMetadata.length} mods when scanning.'); + return modMetadata; + #else + return []; + #end + } + + public static function getAllModIds():Array + { + var modIds = [for (i in getAllMods()) i.id]; + return modIds; + } +}