Properly implemented Polymod for mod loading.

This commit is contained in:
Eric Myllyoja 2022-01-20 17:47:42 -05:00
parent 81f7a04916
commit bfad5b3352
11 changed files with 271 additions and 43 deletions

View file

@ -29,9 +29,6 @@
<!--Mobile-specific-->
<window if="mobile" orientation="landscape" fullscreen="true" width="0" height="0" resizable="false"/>
<!--Switch-specific-->
<window if="switch" orientation="landscape" fullscreen="true" width="0" height="0" resizable="true" />
<!-- _____________________________ Path Settings ____________________________ -->
<set name="BUILD_DIR" value="export/debug" if="debug" />
@ -40,12 +37,9 @@
<classpath name="source" />
<!-- <assets path='assets/preload/music' include="*mp4" embed='false' /> -->
<assets path="assets/preload" rename="assets" exclude="*.ogg" if="web"/>
<assets path="assets/preload" rename="assets" exclude="*.mp3" unless="web"/>
<!-- <define name="PRELOAD_ALL" /> -->
<define name="PRELOAD_ALL" unless="web" />
<define name="NO_PRELOAD_ALL" unless="PRELOAD_ALL"/>
@ -102,9 +96,8 @@
<!-- <assets path='example_mods' rename='mods' embed='false'/> -->
<assets path='example_mods' rename='mods' embed='false'/>
<assets path='example_mods' rename='mods' embed='false' exclude="*.md"/>
<assets path='art/readme.txt' rename='do NOT readme.txt' />
<!-- <template path='mods' /> -->
<assets path="CHANGELOG.md" rename='changelog.txt'/>
@ -223,6 +216,28 @@
<config:ios allow-provisioning-updates="true" team-id=""/>
<!-- Options for Polymod -->
<section if="polymod">
<!-- Turns on additional debug logging. -->
<haxedef name="POLYMOD_DEBUG" value="true" if="debug" />
<!-- The file extension to use for script files. -->
<haxedef name="POLYMOD_SCRIPT_EXT" value=".hscript" />
<!-- Which asset library to use for scripts. -->
<haxedef name="POLYMOD_SCRIPT_LIBRARY" value="scripts" />
<!-- The base path from which scripts should be accessed. -->
<haxedef name="POLYMOD_ROOT_PATH" value="scripts/" />
<!-- Determines the precision required for mods to be compatible. -->
<haxedef name="POLYMOD_API_VERSION_MATCH" value="MATCH_MINOR" />
<!-- Determines the subdirectory of the mod folder used for file appending. -->
<haxedef name="POLYMOD_APPEND_FOLDER" value="_append" />
<!-- Determines the subdirectory of the mod folder used for file merges. -->
<haxedef name="POLYMOD_MERGE_FOLDER" value="_merge" />
<!-- Determines the file in the mod folder used for metadata. -->
<haxedef name="POLYMOD_MOD_METADATA_FILE" value="_polymod_meta.json" />
<!-- Determines the file in the mod folder used for the icon. -->
<haxedef name="POLYMOD_MOD_ICON_FILE" value="_polymod_icon.png" />
</section>
<section if='TOOLS'>
<!-- Compiles tool for old song conversion shit -->
<!-- Assumes you use it on windows/desktop!!!! -->

View file

@ -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

2
example_mods/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
./tricky
./enaSkin

View file

@ -1 +0,0 @@
introMod

View file

@ -1,2 +0,0 @@
THIS MOD FOLDER DOES NOT ENTIRELY WORK JUST YET!!!
DONT EXPECT MUCH OUT OF IT RIGHT NOW!!!

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View file

@ -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"
}

View file

@ -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<String>).
PolymodHandler.loadAllMods();
if (stage != null)
{

View file

@ -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)

13
source/modding/IHook.hx Normal file
View file

@ -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 {}

View file

@ -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; <MAJOR>.<MINOR>.<PATCH>.
* 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<String>)
{
#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<ModMetadata>
{
#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<String>
{
var modIds = [for (i in getAllMods()) i.id];
return modIds;
}
}