Friday Night Funkin' Cookbook
Friday Night Funkin' CookbookIntermediateMigrating Mods to Newer Versions

Migrating Mods to Newer Versions

Reading time: 8 minutes

Occasionally, in order to make improvements to the modding system for Friday Night Funkin', the team has to make breaking changes to features that older mods were using, causing them to have unexpected behavior or otherwise no longer function properly. When we do this, we update the API version rule, automatically flagging any older mods and preventing them from being loaded to ensure the stability of the game.

This chapter will walk you through the process of making older mods compatible with newer versions of Friday Night Funkin'. Once you compete the steps in this guide, your mod should function as expected on newer versions.

Migrating from v0.1.0 to v0.5.0

Rewriting JSON merge files

In v0.5.0, the system for merging into JSON files was fundamentally reworked. Any mods which used this system previously need to refactor these files in order to work properly.

In your mod's _merge folder, look for any json files and rewrite their contents.

// This is the format used by older versions of the game.
{
  "merge": [
    // Set `data.difficulty` to "super_hard"
    {
      "target": "data.difficulty",
      "payload": "super_hard"
    },
    // In the second element of the `data.nested.enemies` array, set `weapon` to "minigun"
    {
      "target": "data.nested.enemies[1].weapon",
      "payload": "minigun"
    }
  ]
}
// This is the format which will be used starting with v0.5.0.
[
  {
    "op": "replace",
    "path": "/playData/characters/opponent",
    "value": "monster"
  }, // Replace the value of opponent with monster.
  { "op": "add", "path": "/playData/characters/girlfriend", "value": "nene" }, // Add a new key girlfriend with the value Nene.
  { "op": "add", "path": "/playData/difficulties/1", "value": "funky" }, // Add a new value funky to the difficulty array, after easy
  { "op": "add", "path": "/playData/difficulties/-", "value": "expert" }, // Add a new value expert to the end of the difficulty array.
  { "op": "remove", "path": "/playData/garbageValue" }, // Remove the key garbageValue from the data entirely
  { "op": "test", "path": "/playData/garbageValue", "value": 37 } // Test that a given value is in the JSON. If this operation fails, the patches will be rejected.
]

More information about this new system can be found at Merging Files.

Removal of Flixel UI

Flixel UI is a library used for developing creating UI elements and managing UI events in HaxeFlixel. In the past, this was used to power the UI of the Chart Editor, but the development team regularly found the library to be frustrating to use, and eventually switched to HaxeUI for most of its user interfaces.

In Friday Night Funkin' v0.5.0, the last places that the game used this library were refactored, and the game now exclusively uses a combination of manual sprite placement and HaxeUI for its user interfaces. As a result, Flixel UI was removed as a dependency.

Any mods which utilized functions and classes provided by Flixel UI may need refactoring to compensate.

Updating the API version

Once all the migration steps above have been performed, the last step is to modify your mod's API version string. In your mod's _polymod_meta.json file, locate the "api_version" property and set it to "0.5.0".

{
  // ...

  // Change this value from "0.1.0" to "0.5.0"
  "api_version": "0.5.0"

  // ...
}

noteNote

For versions of Friday Night Funkin' between v0.3.0 and v0.4.1, the modding system always looked for "0.1.0" for the api_version and refused to load the mod in all other cases. With the v0.5.0 update, this version will now change to match the game version with every update, but only breaking changes will forcibly disable mods. Those breaking changes will be documented on this page.

Migrating from v0.5.0 to v0.6.3

Migration from v0.5.0 to v0.6.3 requires some changes to mods.

Update the API Version

Eric accidentally forgot to increment the API version for the game when updating to v0.6.0, and this got fixed with v0.6.3. This means that mods won't load unless the api_version value in the _polymod_meta.json file is at least v0.6.3. This allows mod creators to perform testing and ensure their mods are compatible before releasing an update on their distribution platforms of choice. Most mods (especially ones with minimal scripting) will need no changes to work as expected, but developers should probably do some playtesting to be sure.

Migrate from hxCodec to hxvlc

The library used for video playback has changed from hxCodec to hxvlc. This has resulted in improved performance and various bug fixes overall, but breaks mods which interact directly with the video library. This should not break any mods which use the built-in system for cutscenes in Funkin'.

Make the following changes:

// BEFORE: Imports from the package `hxcodec`
import hxcodec.flixel.FlxVideoSprite;

// AFTER: Imports from the package `hxvlc`
import hxvlc.flixel.FlxVideoSprite;


// BEFORE: Callback to onTextureSetup
video.bitmap.onTextureSetup.add(() -> { ... });

// AFTER: Callback to onFormatSetup
video.bitmap.onFormatSetup.add(() -> { ... });


// BEFORE: Play a video by path.
video.play(videoPath);

// AFTER: Load a video by path, then play if load was successful.
// You can also run video.load() in advance before playing the video.
if (video.load(videoPath)) video.play();

Options Menu Changes

The options menu received a minor refactor internally. The pages list was moved to its own class, which changes the code needed to access the "Preferences" menu (mainly done to add custom preferences).

We would like to standardize the process of adding custom user preferences to mods in the future eventually, but in the meantime you can make the necessary tweaks:

// BEFORE: Retrieve the value from the page Map.
if (Std.isOfType(currentState, OptionsState)) {
    var preferencesPage = currentState.pages.get("preferences");

    // Create a new option.
    prefs.createPrefItemCheckbox(...);
}

// AFTER: Retrieve the value from the page Map, which is now inside a Codex.
if (Std.isOfType(currentState, OptionsState)) {
    var preferencesPage = currentState.optionsCodex.pages.get("preferences");

    // Create a new option.
    prefs.createPrefItemCheckbox(...);
}

Sticker Changes

v0.6.0 rewrote how stickers get used by the game (and v0.6.3 rewrote it again but better this time). Any existing mods that provided stickers will probably break.

New or updating mods looking to add, remove, or replace stickers should consult the custom Sticker Packs documentation

Migrating from v0.7.5 to v0.8.0

Migrate from FlxAnimate to flixel-animate

The library used for texture atlas rendering has changed from FlxAnimate to flixel-animate. This has resulted in improved performance and lots of bug fixes, but breaks mods which use texture atlas sprites.

The main change that breaks mods is the improved bounds method flixel-animate provides. This provides more accurate values for width and height, but breaks the positions of existing texture atlas sprites.

This will be split in 3 sections: Characters, Playable Characters, and Scripts

Characters

Any characters that use the Animate Atlas render type will need to be manually repositioned. Frame label animations should still work, but symbol animations need to be explicitly defined by setting animType to "symbol" in the animation data.

Playable Characters

For Freeplay DJ data, you can either manually reposition the animations to match the new bounds, or enable applyStageMatrix in the JSON data.

Below is an example on how you can enable applyStageMatrix in the Freeplay DJ data, using Boyfriend's JSON data as an example:

"freeplayDJ": {
  "assetPath": "freeplay/freeplay-boyfriend",

  // Enable applyStageMatrix here to get the old positioning from FlxAnimate
  "applyStageMatrix": true,

  "scriptClass": "BoyfriendFreeplayDJ",
  "cartoon": {
    "soundClickFrame": 80,
    "soundCartoonFrame": 85,
    "loopBlinkFrame": 112,
    "loopFrame": 166,
    "channelChangeFrame": 60
  },
  ...
}

With this change, the animations will be positioned exactly how it was back in v0.7.5.

The same thing applies to Results Screen animations. You can either manually reposition the animations, or enable applyStageMatrix in the JSON data.

Below is an example on how you can enable applyStageMatrix in the Results Screen data, using Boyfriend's JSON data as an example:

"loss": [
  {
    "renderType": "animateatlas",
    "scriptClass": "BFShitResults",
    "zIndex": 500,
    "offsets": [0, 20],
    "loopFrameLabel": "Loop Start",

    // Enable applyStageMatrix here to get the old positioning from FlxAnimate
    "applyStageMatrix": true
  }

Scripts

The class used to make texture atlas sprites has moved from FlxAtlasSprite to FunkinSprite. An import alias was kept for backwards compatibility, however.

Like with playable characters, you can either manually reposition your sprite to match the new bounds, or enable applyStageMatrix.

Below is an example on how you can enable applyStageMatrix in your script:

var bfFakeout:FlxAtlasSprite = new FlxAtlasSprite(this.x - 440, this.y - 240, Paths.animateAtlas("characters/bfFakeOut", "shared"));

// Enable applyStageMatrix here to get the old positioning from FlxAnimate
bfFakeout.applyStageMatrix = true;

GameOverSubState.instance.add(bfFakeout);

Since flixel-animate works more closely with FlxSprites, you now use the standard Flixel animation controller instead of the custom one FlxAnimate provided. You can still use the anim property if you wish, though it's the exact same as animation now.

Here are some examples on how to migrate your sprite to use the normal animation controller:

Before

// A callback fired when the animation finishes
sprite.onAnimationComplete.add((name:String) -> {
  // Some code here
});

// A callback fired when the animation changes frames
sprite.onAnimationFrame.add((name:String, frame:Int) -> {
  // Some code here
});

// A callback fired when the animation loops
sprite.onAnimationLoop.add((name:String) -> {
  // Some code here
});

// Play an animation and loop it
sprite.playAnimation("idle", false, false, true);

// Set the animation frame
sprite.anim.curFrame = 15;

// Get the first frame from the layer "VIZ_bars" from the current timeline
sprite.anim.curSymbol.timeline.get("VIZ_bars").get(0);

After

// A callback fired when the animation finishes
sprite.animation.onFinish.add((name:String) -> {
  // Some code here
});

// A callback fired when the animation changes frames
sprite.animation.onFrameChange.add((name:String, frame:Int, frameIndex:Int) -> {
  // Some code here
});

// A callback fired when the animation loops
sprite.animation.onLoop.add((name:String) -> {
  // Some code here
});

// Play an animation and loop it
sprite.animation.play("idle");
sprite.animation.curAnim.looped = true;

// Set the animation frame
sprite.animation.curAnim.curFrame = 15;

// Get the first frame from the layer "VIZ_bars" from the current timeline
// NOTE: There is no `curSymbol` property, `sprite.timeline` automatically gets the timeline of the current animation.
sprite.timeline.getLayer("VIZ_bars").getFrameAtIndex(0);

New functions

Some new functions were made to FunkinSprite to make texture atlas sprites easier to use. Below are some of the notable ones, the full list can be found in FunkinSprite.

  • createTextureAtlas: Creates a new FunkinSprite with a texture atlas sprite.
var bfFakeout:FunkinSprite = FunkinSprite.createTextureAtlas(this.x - 440, this.y - 240, "characters/bfFakeOut", "shared");
  • loadTextureAtlas: Loads a texture atlas sprite into an existing FunkinSprite instance.
var bfFakeout:FunkinSprite = new FunkinSprite(this.x - 440, this.y - 240);
bfFakeout.loadTextureAtlas("characters/bfFakeOut", "shared");
  • getFirstElement: Returns the first element of a symbol instance.
// From `assets/scripts/players/results/pico/PicoGoodResults.hxc`
var whiteElement:Element = getFirstElement("white small");
var blackElement:Element = getFirstElement("blacksmall");
  • scaleElement: Scales an element by a certain amount, can be used in combination with getFirstElement.
// From `assets/scripts/players/results/pico/PicoGoodResults.hxc`
var whiteElement:Element = getFirstElement("white small");
var blackElement:Element = getFirstElement("blacksmall");

scaleElement(whiteElement, 10, 500);
scaleElement(blackElement, 15, 500);
  • getSymbolElements: Returns every element of a symbol instance.
var elements:Array<Element> = getSymbolElements("white small");
  • replaceSymbolGraphic: Replaces the graphic of a symbol instance. This works similarly to FlxAtlasSprite's replaceFameGraphic method, except you pass a string (which is the name of the symbol) instead of an integer.
// From `funkin/util/plugins/NewgroundsMedalPlugin.hx`, `graphic` here is a `FlxGraphic`
instance.medal.replaceSymbolGraphic("NGMEDAL", graphic);

New settings

Some new settings were added to FunkinSprite for texture atlas sprites. This also applies to character data (see Custom Characters for more information).

Below is a list of all of them and how to use them:

// NOTE: `loadTextureAtlas` also has this!
var sprite:FunkinSprite = FunkinSprite.createTextureAtlas(0, 0, "characters/bfFakeOut", "shared", {
  swfMode: false, // If true, the texture atlas will behave as if it was exported as an SWF file. Notably, this allows MovieClip symbols to play.

  cacheOnLoad: true, // If true, filters and masks will be cached when the atlas is loaded, instead of during runtime.

  filterQuality: 0, // The quality of the filters used in the atlas. It's a number from 0 to 3, with 0 being the highest and 3 being the lowest.

  applyStageMatrix: true, // If true, it applies the stage matrix if it was exported from a symbol instance, positioning the sprite as it would display in Adobe Animate. Turning this on is only recommended if you prepositioned the character in Animate, or need a quick fix for positioning when migrating.

  useRenderTexture: false, // If enabled, the character will render as one texture instead of having each limb render individually. This is useful for things like changing alpha, and shaders that require the whole sprite. Only enable this if your character either changes alpha to something other than 1.0 or has a shader or blend mode that requires the whole sprite.

  spritemaps: null, // Advanced: A list of spritemaps to use for this sprite.

  metadataJson: null, // Advanced: A string of metadata JSON data.

  uniqueInCache: false, // Advanced: If true, the texture atlas will use a new slot in the cache.

  cacheKey: null, // Advanced: Force the cache to use a specific key to index the texture atlas.

  onSymbolCreate: null, // Advanced: A callback that's fired when a symbol instance is created.
});

Character re-exports

A lot of characters were re-exported from sparrows to texture atlases, this was done to improve memory usage throughout the game. Certain mods will need to be updated to accommodate, as the render types for these characters have changed.

The following characters were re-exported:

  • Boyfriend*
  • Boyfriend (Dark)*
  • Boyfriend (Car)*
  • Daddy Dearest
  • Darnell
  • Girlfriend
  • Girlfriend (Dark)
  • Girlfriend (Car)
  • Girlfriend (Christmas)
  • Mommy Mearest (Car)
  • Mommy and Daddy (Festive)
  • Monster
  • Monster (Christmas)
  • Nene
  • Nene (Dark)
  • Otis (Speaker Shooter)
  • Pico (Speaker Shooter)
  • Pico (holding Nene)*
  • Tankman*
  • Tankman (Bloody)*

* Uses the multianimateatlas render type instead of the animateatlas render type.


Contributors:
doggogit
MightyTheArmiddilo
TechnikTil
AbnormalPoof
Pāvels Rimašs
Cameron Taylor
kade-github
Last modified:
Created:
Category:  Intermediate
Tags: