MF_Core — API Reference

Base library for the MF_* plugin family for RPG Maker MZ: utilities, math, layout units, safe hooks, events, validation, conditions, file/save/config storage, editable documents, localization and text codes, input and drag-and-drop, tweens and timers, asset preloading, and layout-aware display primitives.

New here?5-minute quick start and a complete example plugin PitfallsThe non-obvious details, collected in one place Module overviewWhat each of the modules is for

Press / to search every function, option and pitfall on this page.

Overview

Everything is exposed through the global object MF. Modules marked experimental may change in a MINOR release (always listed in the changelog).

ModulePurpose
MF.CorePlugin registration, dependency and version checks, parameter parsing
MF.LogPrefixed console logging
MF.UtilsType checks, clone, merge, diff, path access, ids, versions
MF.MathNumbers, snapping, rectangles, easing
MF.UnitsSize expressions: "240", "50%", "100%-240", "auto", functions and conditions (1.1.0)
MF.AnchorNine anchor points and coordinate conversion
MF.ColorColor parsing and conversion
MF.HookMethod overriding: alias / before / after
MF.EventsEventEmitter and the global bus
MF.DeprecationDeprecation warnings and API redirects
MF.SchemaValidation and normalization of structured data and plugin parameters
MF.ConditionDeclarative conditions (switches, variables, items, …)
MF.ScriptAccess-controlled execution of JS from data
MF.FSFile access (NW.js), JSON loading, downloads
MF.DataExtra data/*.json files loaded with the database
MF.SavePlugin state inside save files
MF.ConfigPlugin settings in config.rmmzsave
MF.MigrationVersioned data format migrations
MF.HistoryUndo/Redo stack
MF.Document experimentalEditable JSON document: undo/redo + validation + safe save
MF.I18nLocalization with plural forms
MF.TextCustom escape codes, message/choice Promises, word wrap
MF.InputActions, raw keys, shortcuts, pointer, drag-and-drop experimental, devices
MF.TickerPer-frame update runner
MF.TweenAwaitable property animation
MF.TimerFrame-based waits, delays, intervals
MF.QueueSequential async task queue
MF.Layout experimentalUnits + Anchor layout for any display object
MF.UI experimentalWindow / Sprite / Container with built-in layout
MF.AssetsBatch preloading with progress and safe failures
MF.Registry 1.2.0Named extension registries (actions, element types, …)
MF.Services 1.2.0Versioned APIs between plugins without hard dependencies
MF.Notetag 1.2.0Typed notetags, blocks, values collected from a battler
MF.GameEvents 1.2.0game:* events: switches, variables, gold, items, battle, map
MF.Commands 1.2.0Plugin commands with typed arguments and async wait
MF.Options 1.2.0Shared Scene_Options entries stored in MF.Config
MF.Modifiers 1.2.0Stacking stat modifiers with priorities
MF.Random 1.2.0Seeded random numbers, streams saved with the game

Pitfalls

Details that are easy to miss when you jump straight to one function. Each item is also marked with a red Pitfall box in its section.

Setup and versions

Text and messages

Time and animation

Data, saves and files

Input and display

Installation

  1. Copy MF_Core.js to js/plugins/.
  2. Enable it in the Plugin Manager.
  3. Place it above all other MF_* plugins.

Requires RPG Maker MZ 1.8+. File-writing features require NW.js (desktop / test play); everything else also works in browser builds.

Parameters

ParameterDefaultDescription
debug (Debug Mode)falsePrints MF.Log.debug messages to the console.
allowScript (Allow Scripts)falseAllows executing JS code stored in data: script conditions and actions. When disabled, scripts return their fallback value and a one-time warning is logged — see MF.Script.

Plugin template

New to MF_Core? The tutorial builds this plugin step by step.

Template for a dependent plugin. Note the explicit MF_Core version in both @help and code — see Versioning policy (Reference).

/*:
 * @target MZ
 * @plugindesc [v0.1.0] Quest log.
 * @author MesaFer
 * @base MF_Core
 * @orderAfter MF_Core
 *
 * @help MF_QuestLog.js
 * Requires: MF_Core 1.0.0+ (written against 1.0.0)
 */
(() => {
    "use strict";
    const PLUGIN_NAME = "MF_QuestLog";

    if (!window.MF || !MF.Core) throw new Error(`${PLUGIN_NAME} requires MF_Core.`);
    MF.Core.register(PLUGIN_NAME, "0.1.0", { requires: { MF_Core: "1.0.0" } });

    const params = MF.Core.parameters(PLUGIN_NAME, {
        type: "object",
        properties: {
            maxQuests: { type: "integer", min: 1, default: 20 }
        }
    });

    MF.Save.register(PLUGIN_NAME, { default: () => ({ quests: {} }), version: 1 });
    const t = MF.I18n.translator(PLUGIN_NAME);
})();
@base and @orderAfter make the MZ Plugin Manager warn about a missing or misplaced MF_Core before the game even starts.

MF.Core

MF.Core.register(name, version, info?)

Registers an MF_* plugin. info.requires — { pluginName: minVersion }, each checked with require() before registration. Other info fields are stored in the registry entry.

MF.Core.register("MF_QuestLog", "0.1.0", { requires: { MF_Core: "1.0.0" } });
MF.Core.require(requester, name, minVersion) → true

Throws an Error (shown by MZ on screen) when name is not registered or its version is lower than minVersion. Logs a warning when minVersion is omitted, and a one-time compatibility warning when the installed MAJOR version is higher than the required one.

MF.Core.isRegistered(name, minVersion?) → boolean
MF.Core.version(name) → string | null
MF.Core.list() → Array<{ name, version, … }>
MF.Core.parameters(pluginName, schema?) → object

Same as PluginManager.parameters, but recursively parsed with MF.Utils.parseParams. With a schema, values are validated and defaults are applied; problems are logged as one warning listing all fields.

PropertyDescription
MF.Core.VERSIONVersion string of MF_Core
MF.Core.params{ debug, allowScript }

MF.Log

MF.Log.debug(tag, ...args)   // only when the "debug" parameter is enabled
MF.Log.info(tag, ...args)
MF.Log.warn(tag, ...args)
MF.Log.error(tag, ...args)

Output is prefixed with [tag], e.g. [MF_UIEditor] layout applied.

MF.Utils

Type checks

FunctionReturns true when
isNumber(v)finite number (not NaN / Infinity)
isString(v)string
isFunction(v)function
isObject(v)non-null object that is not an array
isPlainObject(v)object literal / Object.create(null)
isEmpty(v)null, undefined, empty string, empty array or object without keys

Runtime mode

FunctionDescription
isTest()Test play (test option)
isBattleTest()Battle test
isEventTest()Event test
isNwjs()Running in NW.js (desktop)

Data

clone(value) → copy

Deep copy of plain data. Class instances are returned by reference.

merge(...sources) → new object

Deep merge. Arrays are replaced as a whole; undefined values are skipped, null is written. Arguments are not mutated.

MF.Utils.merge({ a: { b: 1, c: 2 } }, { a: { c: 3 } });  // { a: { b: 1, c: 3 } }
equals(a, b) → boolean

Deep equality of plain data (NaN equals NaN).

diff(base, target) → object

Returns only the fields of target that differ from base. Always returns an object; nested objects without changes are omitted. Used to store layouts as overrides of the defaults.

MF.Utils.diff({ a: { b: 1 } }, { a: { b: 1 } });        // {}
MF.Utils.diff({}, { a: 5 });                            // { a: 5 }
MF.Utils.diff({ a: 5 }, { a: {} });                     // { a: {} }
MF.Utils.diff({ w: { x: 1, y: 2 } }, { w: { x: 5, y: 2 } }); // { w: { x: 5 } }
PitfallOverlay semantics. merge(base, diff(base, target)) equals target as long as target does not remove keys that exist in base. Removals are not representable — use explicit values such as visible: false. More

Path access

Paths are dot strings ("a.b.0.c") or arrays (["a", "b", 0, "c"]).

FunctionDescription
get(obj, path, fallback?)Value at path or fallback
set(obj, path, value)Writes the value, creating objects/arrays on the way (numeric key → array). Returns obj
has(obj, path)Own property exists along the whole path
unset(obj, path)Deletes the last key
toPath(path)Normalizes to an array

Misc

FunctionDescription
uid(prefix = "mf")Session-unique id, e.g. mf_lx3k2a1
uniqueName(base, existing)"window", "window_2", … not present in existing (array or Set)
parseParams(value)Recursively parses MZ plugin parameters: JSON strings, "true"/"false", numbers. Since 1.2.0 strings with leading zeros ("007", "01") stay strings
compareVersions(a, b)-1, 0 or 1 for "1.2.3"-style versions
debounce(fn, ms)Debounced function with .cancel()
parseJson(text, fallback = null)JSON.parse that never throws
format(template, data)"Hello {name}" substitution; supports paths {actor.name}

MF.Math

Numbers

FunctionDescription
clamp(v, min, max), clamp01(v)Limit to range
lerp(a, b, t)Linear interpolation
invLerp(a, b, v)Inverse: position of v between a and b
remap(v, inMin, inMax, outMin, outMax)Map from one range to another
roundTo / floorTo / ceilTo(v, step)Round to step: roundTo(13, 5) → 15
toFixed(v, digits = 2)Round to decimals (returns a number)
approx(a, b, eps?)Approximate equality
wrap(v, min, max)Cyclic value in [min, max): wrap(-1, 0, 5) → 4
randomInt(min, max), randomFloat(min, max)Random values (int inclusive)
distance(x1, y1, x2, y2)Euclidean distance
degToRad(d), radToDeg(r)Angle conversion

Snapping

snap(value, targets, threshold) → { value, snapped, target }

Snaps to the nearest target if it is within threshold. Intended for editor guides.

MF.Math.snap(13, [0, 15, 408], 4);  // { value: 15, snapped: true, target: 15 }

Rectangles

Rectangles are plain objects { x, y, width, height }.

FunctionDescription
rect(x, y, w, h)Create
rectRight(r), rectBottom(r), rectCenter(r)Edges and center
rectContains(r, px, py)Point inside
rectIntersects(a, b)Overlap test
rectIntersection(a, b)Overlap rectangle or null
rectUnion(a, b)Bounding rectangle
rectNormalize(r)Fix negative width/height
rectClampInside(r, bounds)Keep inside bounds
rectEquals(a, b)Equality

Easing

Functions in MF.Math.easing take t in [0, 1]: linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInSine, easeOutSine, easeInOutSine, easeOutBack, easeOutBounce.

MF.Math.ease("easeOutQuad", 0.5);           // 0.75 (t is clamped, unknown name → linear)
MF.Math.tween(0, 100, 0.5, "easeOutQuad");  // 75
MemberDescription
easingObject of easing functions t => value
ease(name, t)Apply an easing by name; t is clamped, unknown names fall back to linear
tween(a, b, t, easing = "linear")Eased interpolation between a and b (a pure calculation; for animation over frames see MF.Tween)
EPSILON1e-6, default tolerance of approx

MF.Units

Parses size and position expressions without eval. Compiled expressions are cached.

SyntaxMeaning
240, "240", "240px"Pixels
"50%"Percent of base
+ - * /, parentheses, unary minusArithmetic: "100%-240", "(100%-20)/2"
"auto"Resolved by the auto option
resolve(value, base, options?) → number
OptionDefaultDescription
auto—Number or function used for "auto"
fallback0Used for empty/invalid values (a warning is logged for invalid ones)
roundtrueRound the result; pass false to keep fractions
MF.Units.resolve("100%-240", Graphics.boxWidth);                  // 576 at 816
MF.Units.resolve("(100%-20)/2", 820);                             // 400
MF.Units.resolve("auto", 0, { auto: () => win.fittingHeight(4) });
MF.Units.resolve("50% +", 100, { fallback: 0 });                  // 0 + warning
FunctionDescription
isAuto(v)v === "auto"
AUTOThe constant "auto"
isRelative(v)String containing %
isValid(v)Syntax check without evaluation
compile(v)Returns base => number; throws on syntax errors
toPercent(px, base, digits = 2)toPercent(408, 816) → "50%"
FUNCTIONS, CONSTANTS 1.1.0Frozen lists of expression function / constant names (for editors, autocompletion)
arity(name) 1.1.0{ min, max } argument count of a function, or null

Division by zero yields 0.

Functions, comparisons and conditions 1.1.0

GroupSyntax
Operators^ (power), < > <= >= == !=, && || ! (result 1 / 0), cond ? a : b
Mathmin, max, abs, sign, floor, ceil, round(v, digits?), trunc, sqrt, pow, exp, log, mod (never negative), clamp(v, a, b), lerp(a, b, t), step(edge, v), between(v, a, b), if(c, a, b), hypot, dist(x1, y1, x2, y2)
Anglesradians: sin, cos, tan, asin, acos, atan, atan2(y, x); degrees: sind, cosd, tand, atan2d(y, x), angle(x1, y1, x2, y2) (0° = right, 90° = down); deg(rad), rad(deg)
Constantspi / PI, e, true = 1, false = 0

Precedence (low → high): ? :, ||, &&, comparisons, + -, * /, unary - !, ^. if and ? : evaluate only the chosen branch. Unknown names, unknown functions and wrong argument counts are compile errors (isValid → false). Expressions valid in 1.0.0 give the same results.

MF.Units.resolve("clamp(50%, 200, 400)", 1000);                   // 400
MF.Units.resolve("angle(0, 0, 10, 10)", 0);                       // 45
MF.Units.resolve("3 > 2 ? 10 : 20", 0);                           // 10

MF.Anchor

Anchor names: top-left (default), top, top-right, left, center, right, bottom-left, bottom, bottom-right.

Offsets x/y are measured from the container's anchor point, and the element is aligned by the same point. For example, center with x = 0, y = 0 centers the element; bottom-right with x = -10 places it 10 px from the right edge.

toAbsolute(anchor, x, y, width, height, containerW, containerH) → { x, y }
toRelative(anchor, absX, absY, width, height, containerW, containerH) → { x, y }
MF.Anchor.toAbsolute("center", 0, 0, 100, 50, 816, 624);        // { x: 358, y: 287 }
MF.Anchor.toRelative("bottom-right", 716, 574, 100, 50, 816, 624); // { x: 0, y: 0 }
MemberDescription
names()All nine anchor names
isValid(name)Known anchor name
factors(name)[ax, ay] in 0..1, e.g. center → [0.5, 0.5] (unknown → default)
DEFAULT"top-left"

MF.Color

Accepted inputs: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(r, g, b), rgba(r, g, b, a), or an object { r, g, b, a? }.

FunctionResult
parse(c){ r, g, b, a } (a in 0..1) or null
isValid(c)boolean
toHex(c, withAlpha = false)"#ff0010"
toRgba(c)"rgba(255, 0, 16, 1)" — usable as a Bitmap color
toNumber(c)0xRRGGBB for PIXI tint (invalid → white)
lerp(a, b, t)Blended rgba() string
withAlpha(c, a)Same color with new alpha
toTone(c, gray = 0)[r, g, b, gray] for setColorTone / setBlendColor

MF.Hook

Standardized method overriding with a registry of who hooked what.

alias(target, name, wrapper, owner) → boolean

wrapper(original, ...args) is called with the object's this. Calling original() without arguments passes the original arguments; pass arguments explicitly to change them.

If the method is inherited (e.g. Window_Message.prototype.processCharacter comes from Window_Base), original() calls the parent's implementation as it is at call time, so a plugin that patches Window_Base later is not bypassed — a classic MZ plugin-order bug.

MF.Hook.alias(Scene_Menu.prototype, "create", function(orig) {
    orig();
    this.addChild(new Sprite());
}, "MF_UIEditor");
before(target, name, fn, owner, options?) → boolean

Calls fn(...args) first. Returning false skips the original.

after(target, name, fn, owner, options?) → boolean

Calls fn(result, ...args) after the original. Returning anything other than undefined replaces the result.

Error policy and safe mode

By default errors propagate, like any MZ alias: the game shows the error screen, which is what you want for bugs in core logic. This is deliberate — unlike event listeners, hooks are part of the engine's control flow, and silently swallowing an error there could leave a scene half-initialized.

{ safe: true } (for before / after) isolates errors thrown by fn itself: they are logged once per hook, and the original behavior/result is kept. Errors from the original method or other plugins in the chain are never swallowed.

// A cosmetic extension must never break the menu
MF.Hook.after(Scene_Menu.prototype, "create", function() {
    this.addDecorations();
}, "MF_Decor", { safe: true });
PitfallUse safe only for optional, cosmetic additions — it hides errors of your function. alias has no safe mode because the wrapper controls the original call. More
list() → Array<{ name, owner }>

Installed hooks, for compatibility debugging.

MF.Events

new MF.Events.EventEmitter()
MF.Events.bus — global EventEmitter shared by all MF_* plugins
const emitter = new MF.Events.EventEmitter();
const off = emitter.on("change", (a, b) => { … }, context);  // returns an unsubscribe function
emitter.once("ready", fn);
emitter.emit("change", 1, 2);   // → true if there were listeners
emitter.off("change", fn);      // remove one listener
emitter.off("change");          // remove all listeners of an event
emitter.off();                  // remove everything
emitter.listenerCount("change");
off();

Listener errors are caught and logged so one broken listener does not stop the others. The global bus is MF.Events.bus — see Bus events.

MF.Deprecation

Tools for the deprecation lifecycle. Every deprecated API is reported once per session (console warning + mf:deprecated bus event); further calls are counted.

method(obj, oldName, info) → boolean

With info.fn — obj[oldName] becomes a redirect to info.fn (renamed API). Without it — the existing obj[oldName] is wrapped (API scheduled for removal).

property(obj, oldName, newName, info?) → boolean

Redirects get/set of obj[oldName] to obj[newName].

warn(id, info?)

Manual report, e.g. for a deprecated option value or data format.

list() → Array<{ id, since, removeIn, replacement, owner, note, count }>
info fieldDescription
idDisplay name, e.g. "MF.Utils.oldName" (default: oldName)
sinceVersion in which the API was deprecated
removeInVersion in which it will be removed
replacementWhat to use instead
ownerPlugin name (default MF_Core) — other MF_* plugins can use the same tool
noteExtra text
fn(method only) new implementation

MF.Schema

Validates and normalizes structured data: plugin parameters (@type struct), layout files, config values.

const schema = {
    type: "object",
    properties: {
        count:  { type: "integer", min: 1, max: 10, default: 3 },
        mode:   { type: "string", enum: ["fast", "slow"], required: true },
        size:   { type: "units", default: "100%" },
        anchor: { type: "anchor", default: "center" },
        tint:   { type: "color", nullable: true },
        items:  { type: "array", items: { type: "number" }, default: [] }
    },
    additionalProperties: false
};

MF.Schema.validate(data, schema);   // { ok, errors: [{ path: "items[1]", message: "must be a number" }] }
MF.Schema.normalize(data, schema);  // { ok, value, errors } — defaults applied, "12" → 12, "true" → true
MF.Schema.assert(data, schema, "MF_QuestLog parameters");  // value, or throws listing all problems
validate(value, schema) → { ok, errors: [{ path, message }] }

Validation only; the value is not changed.

normalize(value, schema) → { ok, value, errors }

Validation plus defaults and coercion of string values ("12" → 12, "true" → true, JSON strings → objects). The input is not mutated.

assert(value, schema, label?) → normalized value

Like normalize, but throws an Error listing all problems.

Schema fields

FieldApplies toDescription
typeallany (default), number, integer, string, boolean, array, object, units, color, anchor, condition, or a registered type
defaultallValue or factory function; applied by normalize when the value is missing
requiredobject propertiesMissing value is an error
nullableallnull is allowed
enumallAllowed values (deep equality)
oneOfallAlternative schemas; the first that matches wins
validateallfn(value) → true | "error message"
coerceallfalse disables string coercion in normalize
min, maxnumber, integerRange
minLength, maxLength, patternstringLength, RegExp or string pattern
items, minItems, maxItemsarrayItem schema and size
properties, additionalPropertiesobjectProperty schemas; unknown keys: true (kept, default), false (error) or a schema
register(type, handler(value, schema, ctx))

Custom type. Report problems with ctx.error(message), validate nested values with ctx.child(value, schema, key), return a normalized value or undefined.

MF.Schema.register("actorId", (v, s, ctx) => {
    if (!Number.isInteger(v) || !$dataActors[v]) ctx.error(`unknown actor ${v}`);
});
format(errors) → string

MF.Condition

evaluate(cond, context?) → boolean

null, undefined and true are true; an array is treated as all.

ConditionTrue when
{ switch: 1 }Switch 1 is ON (value: false → OFF)
{ variable: 5, op: ">=", value: 3 }Variable comparison; valueVariable: n compares with another variable. Operators: == != > >= < <= (default ==, strict)
{ selfSwitch: [mapId, eventId, "A"] }Self switch is ON (value: false → OFF)
{ partyHas: 3 }Actor 3 is in the party
{ item: 7, count: 2 }Party has at least 2 of item 7 (also weapon, armor; default count 1)
{ gold: 100, op: ">=" }Gold comparison (default >=)
{ script: "…" }Script returns truthy — requires Allow Scripts
{ all: [...] }, { any: [...] }, { not: {...} }Logical combinations
MF.Condition.evaluate({ all: [
    { switch: 1 },
    { variable: 5, op: ">=", value: 3 },
    { not: { partyHas: 2 } }
]});

Unknown conditions and handler errors evaluate to false and are logged once per unique condition (conditions are often evaluated every frame).

register(key, fn(cond, context))

Adds a condition type detected by the presence of key:

MF.Condition.register("level", c => $gameParty.leader().level >= c.level);
MF.Condition.evaluate({ level: 10 });
dependencies(cond) → { switches: Set, variables: Set }

Switch and variable ids a condition depends on — lets UIs refresh on change instead of every frame.

compare(a, op, b) → boolean

MF.Script

PitfallDisabled by default: script conditions are false until Allow Scripts is on (warning logged once). More
run(code, context?, fallback?) → any

code is an expression ("a + b") or a block with return. Keys of context are available as variables; context.self becomes this. Compiled functions are cached.

MF.Script.run("a + b", { a: 1, b: 2 });                 // 3
MF.Script.run("const x = a * 2; return x;", { a: 4 });  // 8

When scripts are disabled

With Allow Scripts = false, run returns fallback (for conditions: false). To avoid the "condition silently doesn't work" trap:

Runtime errors behave the same way (mf:scriptError, logged once).

FunctionDescription
isAllowed()Value of the allowScript parameter
validate(code, argNames?){ ok } or { ok: false, message }. Syntax check without execution; works even when scripts are disabled — editors should use it to warn authors up front
compile(code, argNames?)Cached Function; throws on syntax errors
issues()[{ type: "blocked" | "error", code, message, count }]
clearIssues()Reset the list (and allow the warnings to be logged again)
Scripts run with full access to the game and, in NW.js, to Node APIs. Only enable them for data you author yourself.

MF.FS

Relative paths are resolved from the project root (the folder containing index.html).

NW.js only

FunctionDescription
isAvailable()Node file system is accessible
projectRoot()Absolute project root (cached)
resolve(relPath)Absolute path
exists(relPath)File exists
ensureDir(relDir)Create directory recursively
readText(relPath) / readJson(relPath, fallback)Synchronous read; null/fallback if missing or invalid
writeText(relPath, text, options) / writeJson(relPath, data, options)Atomic write (temp file → rename). backup: true keeps the previous version as name.bak.json; indent (default 2) for JSON. Returns boolean
remove(relPath)Delete file
listFiles(relDir, extension?)File names in a directory

projectRoot() tries, in order: process.mainModule.filename (what MZ core uses; deprecated in newer Node), the file:// page location, then process.cwd().

Everywhere

loadJson(url) → Promise<any>

Rejected errors carry err.code:

codeMeaning
notFound404, empty file:// response or network error
httpOther HTTP error status
parseFile exists but contains invalid JSON
download(filename, text, mime?)

Triggers a browser download.

saveJson(relPath, data, options?) → boolean

Writes via fs when available, otherwise downloads the file.

MF.Data

Registers extra JSON files that are loaded together with the database (DataManager.loadDatabase). The game does not start until they are loaded.

register(globalName, src, options?)
OptionDefaultDescription
optionaltrueA missing file is not an error
default{}Value used when an optional file is missing or invalid (cloned)
onLoad—data => data post-processing (migrations, validation). Throwing marks the file as invalid
MF.Data.register("$dataUILayouts", "UILayouts.json", {
    optional: true,
    default: { version: 1, scenes: {} },
    onLoad: data => MF.Migration.migrate("UILayouts", data, 1)
});

Error handling

SituationResult
Optional, file missingDefault value, debug log only
Optional, invalid JSON / HTTP error / onLoad threwDefault value, console error, status().error set, usedDefault = true, mf:dataError emitted
Required, any errorStandard MZ load error screen with Retry. The database is not reported as loaded until the file loads successfully
PitfallAn editor must check MF.Data.status(name) before saving. If the file failed to parse, the global holds the default value, and saving would overwrite the user's (broken but recoverable) file. Use writeJson(…, { backup: true }) in any case — or MF.Document, which does all of this. More
status(globalName) → { state, error, usedDefault } | null

state: idle | pending | loaded | failed. error: { code, message } or null; codes are those of loadJson plus process (onLoad threw).

reload(globalName) → Promise<value>

Reloads a file (e.g. after saving in the editor). Rejects on any error except "optional file not found"; the rejected error has code.

isLoaded() → boolean
checkError()

Throws a pending required-file error in the ["LoadError", url, retry] format expected by SceneManager. Called automatically from DataManager.isDatabaseLoaded.

MF.Save

PitfallStore plain JSON-like data only; module-level variables are not saved at all. More

Plugin state stored inside the player's save file — no manual patching of DataManager.makeSaveContents / extractSaveContents. Data lives under contents.mf[key] = { v, data }.

MF.Save.register("MF_QuestLog", {
    default: () => ({ quests: {}, tracked: null }),
    version: 2,
    migrate: (data, fromVersion) => {
        if (fromVersion < 2) data.tracked = null;
        return data;
    }
});

const state = MF.Save.get("MF_QuestLog");   // mutable reference
state.quests.q1 = { stage: 2 };
MF.Save.register(key, options?)

Registers a save slot for a plugin (use the plugin name as key). Call at plugin load time.

OptionDefaultDescription
default{}Value (cloned) or factory. Used for a new game and for saves without this key
version1Data format version written into the save
migrate(data, fromVersion)—Called when the save has an older version
mergeDefaultstrueDeep-merge defaults under loaded plain-object data, so new fields appear in old saves
onSave(data)—Before writing; return a value to store instead
onLoad(data)—After reading, migration and merging
MethodDescription
get(key)Current data (created from default if needed)
set(key, value)Replace data
reset(key)Back to default
isRegistered(key), keys()Registry

Lifecycle

Engine callMF.Save
DataManager.createGameObjects (new game, battle/event test, before loading)All keys reset to defaults
DataManager.makeSaveContentsData written to contents.mf
DataManager.extractSaveContentsData read, migrated, merged; mf:saveLoaded emitted

MF.Config

Global settings (not per save) stored in config.rmmzsave under config.mf — e.g. UI theme, window opacity, language.

MF.Config.register("MF_UIEditor.theme", "default");
MF.Config.register("MF_UIEditor.opacity", 192, { schema: { type: "integer", min: 0, max: 255 } });

MF.Config.get("MF_UIEditor.opacity");               // 192
MF.Config.set("MF_UIEditor.opacity", 128, { save: true });
MethodDescription
register(key, default, { schema? })Stored values failing the schema fall back to the default (warning)
get(key)Current value
set(key, value, { save? })Emits mf:configChanged (key, value, old) when changed; save: true writes the config file
reset(key, { save? })Back to default
save()ConfigManager.save()
isRegistered(key), keys()Registry

Use PluginName.setting keys. Unknown keys are preserved. Reserved key: MF_Core.locale (see I18n).

MF.Migration

register(key, fromVersion, fn)

Registers a step from fromVersion to fromVersion + 1. fn(data) may mutate and/or return the data.

migrate(key, data, targetVersion) → data

Applies steps using data.version (missing → 0) and updates it. Stops with a warning if a step is missing.

MF.Migration.register("UILayouts", 1, data => {
    data.themes = data.themes || {};
    return data;
});
MF.Migration.migrate("UILayouts", { version: 1 }, 2);  // { version: 2, themes: {} }

MF.History

Undo/Redo stack. Extends EventEmitter and emits "change" on every modification.

A command is { label?, do(), undo(), merge?(next) }. merge may absorb the next command (e.g. consecutive drag steps) and return true; commands are never merged across a save point.

const history = new MF.History(100);   // depth limit

history.execute({
    label: "move",
    do()   { win.x = 100; },
    undo() { win.x = 0; }
});

history.beginBatch();
/* several history.execute(...) */
history.endBatch("align");             // one undo entry

history.undo(); history.redo();
history.canUndo(); history.canRedo();
history.markSaved(); history.isDirty();
history.clear();
history.on("change", updateToolbar);
MethodDescription
execute(cmd)Runs cmd.do() and records it
push(cmd)Records an already executed command
beginBatch() / endBatch(label)Group commands into one entry
undo() / redo()Return false when there is nothing to do
markSaved() / isDirty()Unsaved changes tracking

MF.Document experimental

The editor workflow in one object: History (undo/redo) + Schema (validation before writing) + FS (atomic save with backup) + Data (runtime global) + Migration. A UI editor edits a document instead of wiring these modules by hand.

const doc = new MF.Document({
    path: "data/UILayouts.json",        // file to edit
    global: "$dataUILayouts",           // MF.Data global, updated on save
    schema: layoutSchema,               // validated on load (defaults) and before save
    version: 1, migrationKey: "UILayouts",
    initial: () => ({ version: 1, scenes: {} }),
    historyLimit: 200,
    autosave: false                     // or debounce time in ms
}).load();

// Dragging: every frame sets x; one undo step thanks to the merge key
drag.onMove = c => doc.set("scenes.Scene_Menu.windows._goldWindow.x", baseX + c.dx, { merge: "drag-gold" });

doc.transaction("Align left", d => {
    d.set("scenes.Scene_Menu.windows._goldWindow.x", 0);
    d.set("scenes.Scene_Menu.windows._commandWindow.x", 0);
});

if (MF.Input.isShortcut("Mod+Z")) doc.undo();
if (MF.Input.isShortcut("Mod+S")) {
    const r = doc.save();
    if (!r.ok) showErrors(r.reason, r.errors || r.error);
}

Loading

load() reads, in order: the file (NW.js) → the runtime global (browser; ignored if it holds only the default) → initial(). Then migration and schema normalization (defaults) are applied and the history is cleared.

If the file exists but is not valid JSON, loadError is set, data falls back to initial(), and save() refuses with reason: "loadError" unless called with { force: true }. The user's broken file is never overwritten silently.

Methods

MethodDescription
load(), revert()(Re)load, discarding unsaved changes
get(path, fallback?), has(path)Read (returns a copy)
set(path, value, { label?, merge? })Undoable write. Consecutive set calls on the same path with the same merge key form one undo step. No-op (returns false) if the value is unchanged
remove(path, { label? })Undoable delete
update(path, fn, options?)set(path, fn(copy))
transaction(label, fn(doc))Several edits → one undo step
undo(), redo(), canUndo(), canRedo(), isDirty()History
validate(){ ok, errors }
save({ force? })→ { ok } or { ok: false, reason: "invalid" | "loadError" | "writeFailed", errors?, error? }. Writes atomically with *.bak.json backup (NW.js) or downloads the file (browser), updates the global, marks history as saved

Properties: data, loadError, history, path, global, schema. Events: load (data, loadError), change (path, value), dirty (isDirty), save (data), saveFailed (reason, details).

MF.I18n

Namespaced translations. Keys are "namespace:path.to.key".

MF.I18n.register("MF_QuestLog", {
    en: {
        title: "Quests",
        greet: "Hello, {name}!",
        count: { zero: "No quests", one: "{count} quest", other: "{count} quests" }
    },
    ru: {
        title: "Задания",
        count: { one: "{count} задание", few: "{count} задания", many: "{count} заданий", other: "{count} задания" }
    }
});

const t = MF.I18n.translator("MF_QuestLog");
t("title");                   // "Задания" (locale ru-RU)
t("count", { count: 5 });     // "5 заданий"
t("greet", { name: "Reid" }); // "Hello, Reid!" — falls back to en
MF.I18n.t("MF_QuestLog:title");

Locale

Current locale, by priority: setLocale() → player choice saved in config (MF_Core.locale) → $dataSystem.locale → browser language → fallbackLocale ("en"). Locale names are normalized: ru_RU → ru-RU.

Lookup chain: ru-RU → ru → en.

Plural forms

If the value is an object and params.count is a number, the form is chosen with Intl.PluralRules of the matched locale (zero, one, two, few, many, other). zero is used for 0 when present; other is the fallback.

MethodDescription
register(namespace, { locale: dict })Deep-merges into existing translations
load(namespace, locale, url)Loads a JSON dictionary → Promise
t(key, params?, { fallback? })Missing key → fallback or the key itself; warned once
translator(namespace)Shortcut function
has(key)Exists in the lookup chain
locale(), detectLocale(), chain(), locales()Locale info
setLocale(locale, { persist? })null resets to automatic. Emits mf:localeChanged (locale, old)
missing()Keys reported missing in this session
fallbackLocaleProperty, default "en"
Translated strings may contain MZ escape codes (\C[2], \V[1]) — they are left untouched for drawTextEx.

MF.Text

PitfallIn JavaScript strings write "\\C[2]"; in the event editor \C[2]. More

Extends RPG Maker's text system (all windows using drawTextEx, including Window_Message) with custom escape codes, plus Promise-based messages for visual-novel style plugins.

Built-in codes

CodeKindEffect
\TR[ns:key]macroI18n translation (may contain other codes)
\SAVE[key:path]macroValue from MF.Save data, e.g. \SAVE[MF_QuestLog:stats.done]
\W[n]escapeWait n frames (message window)
\SE[name,volume,pitch,pan]escapePlay a sound effect at this point of the text (message window)
\FACE[name,index]escapeChange the face mid-message; text pauses until it is loaded; \FACE[] removes it (message window)
\SPD[n]escapen extra frames per character until the end of the message; \SPD[0] = normal; skipped by fast-forward (message window)
Pitfall\FACE does not move text: start the message with a face (or reserve space) if faces are switched later. More

Custom codes

Names are letters only (RPG Maker parses codes as [A-Z]+) and cannot be the reserved V N P G C I PX PY FS.

registerMacro(name, fn(arg, window) → string)

Text replacement before RPG Maker's conversion, so the result may contain \C[n], \V[n] and other macros (expanded up to 10 levels). Parameters cannot contain ].

MF.Text.registerMacro("QUEST", id => MF.Save.get("MF_QuestLog").quests[id]?.title || "???");
// "Current quest: \QUEST[q1]"
registerEscape(name, fn(ctx))

Executed while the text is processed. ctx: { code, param, args, textState, window, drawing, isMessage }.

MF.Text.registerEscape("SHAKE", ctx => {
    if (!ctx.drawing || !ctx.isMessage) return;        // no side effects while measuring text!
    $gameScreen.startShake(Number(ctx.args[0]) || 5, 5, 20);
});
// "Watch out!\SHAKE[7] The ground trembles."
PitfallRPG Maker also runs escape codes when it only measures text (textSizeEx, e.g. for choice widths and word wrap). Side effects must check ctx.drawing, and message-only effects ctx.isMessage. Built-in escapes do both. More

Messages as Promises

Need a scene with a message window (map, battle). A running message is awaited first.

await MF.Text.show("Where am I?", { face: ["Actor1", 0], speaker: "Reid" });
const answer = await MF.Text.choice(["Stay", "Leave"], { text: "What now?", cancel: 1 });
if (answer === 1) await MF.Text.show("Let's go.");
FunctionDescription
show(text, { face, background, position, speaker })Resolves true when closed, false on scene change
choice(choices, { text, default, cancel, background, position, messagePosition, face, speaker })Resolves the chosen index; cancel: -1 disables cancel, an index makes cancel select it; null on scene change

Utilities

FunctionDescription
wrap(window, text, maxWidth?)Lines that fit the width (default innerWidth), measured with textSizeEx so codes don't count; words longer than the line are broken by characters (works for CJK)
strip(text)Remove all escape codes
measure(window, text){ width, height }
applyMacros(text, window?)Expand macros manually
hasCode(name)Registered?

MF.Input

A layer over RPG Maker's Input and TouchInput. Updated once per frame right after RPG Maker reads input.

Actions

PitfallQ, W, X, Z, arrows, Space, Enter, Esc, Shift… are already bound by RPG Maker; bind skips them unless override: true. More
MF.Input.bind("journal", { keys: ["j"], buttons: [8] });   // J key, gamepad "back"
if (Input.isTriggered("journal")) SceneManager.push(Scene_Journal);
if (MF.Input.isReleased("journal")) { … }
FunctionDescription
bind(action, { keys, buttons, override? })Adds to Input.keyMapper / gamepadMapper. Keys: names ("a", "f5", "space", "left") or keyCodes. Keys already bound to another action are skipped with a warning unless override: true
isPressed / isTriggered / isRepeated / isLongPressed(action)Same as Input.*
isReleased(action), track(action)Released this frame (tracking starts on first use or track)
keyCode(name)Name → keyCode

Raw keys and shortcuts

Raw keys use KeyboardEvent.code ("KeyZ", "Digit1", "F5", "ControlLeft") — independent of keyboard layout and of Input.keyMapper. Intended for editors and debug tools.

if (MF.Input.isShortcut("Mod+Z")) doc.undo();          // Ctrl+Z, Cmd+Z on macOS
if (MF.Input.isShortcut("Mod+Shift+Z")) doc.redo();
if (MF.Input.isShortcut("Delete")) removeSelected();
FunctionDescription
isKeyDown / isKeyTriggered / isKeyReleased(code)Raw state; triggered/released last exactly one frame
isShortcut(combo, { whileTyping? })Triggered this frame with exactly these modifiers, e.g. "Mod+Z", "Ctrl+Shift+Z", "F5". Tokens: Ctrl, Shift, Alt, Meta/Cmd, Mod; letters, digits, F1–F12, Esc, Enter, Space, Tab, Delete, Backspace, arrows (Up…), PageUp, Home… or any KeyboardEvent.code. Ignored while typing in an HTML field
modifiers(){ ctrl, shift, alt, meta }
isTyping()Focus is in an HTML input/textarea/select/contentEditable

Pointer

FunctionDescription
pointer(){ x, y, pressed, triggered, released, clicked, repeated, longPressed, cancelled, doubleClicked, wheelX, wheelY, consumed } — raw values even when consumed
isDoubleClicked()Two clicks within 20 frames and 8 px
consumePointer(), isPointerConsumed()For the rest of this frame TouchInput.* returns false, so windows and the map ignore a pointer action already handled
hitTest(displayObject, x, y)Point inside the object's global bounds (visible objects only)

Drag-and-drop experimental

PitfallWhile dragging, TouchInput.* returns false for the game; read raw values with MF.Input.pointer(). More
MF.Input.draggable(target, options?) → { destroy(), setEnabled(bool), isDragging() }
let base;
const handle = MF.Input.draggable(window, {
    onStart: () => (base = { x: window.x, y: window.y }),
    onMove: c => { window.x = base.x + c.dx; window.y = base.y + c.dy; },
    onEnd: () => saveLayout(),
    onCancel: () => { window.x = base.x; window.y = base.y; },   // right click during drag
    onClick: () => select(window)
});
OptionDefaultDescription
threshold4Pixels before a press becomes a drag (otherwise onClick)
priority0Overlapping targets: higher priority wins, then the most recently registered
consumetrueConsume the pointer while dragging and on release
persistentfalseKeep across scene changes (default: removed with the scene)
hitTest(x, y, target)boundsCustom hit area (e.g. only a title bar)
onPress, onStart, onMove, onEnd, onClick, onCancel—ctx = { target, x, y, startX, startY, dx, dy, moveX, moveY }

Handle: destroy(), setEnabled(bool), isDragging(). Destroyed targets are removed automatically.

MF.Input.isDragging() → boolean — any drag in progress

Devices

FunctionDescription
lastDevice()"keyboard" | "mouse" | "touch" | "gamepad" — e.g. to show the right button prompts. Event mf:inputDeviceChanged (device, old)
gamepads()[{ index, id, mapping }]
vibrate({ duration = 200, strong = 1, weak = 1 })Rumble where the browser/pad supports it; returns true if accepted

MF.Ticker

Per-frame runner used by MF.Tween and MF.Timer. It ticks once per game frame (60 fps) after the scene update, only while the scene is started and the game window is active — animations pause together with the game.

add(fn(frame), options?) → { remove(), isActive() }

Return false from fn to stop. A throwing callback is logged and removed.

OptionDescription
persistentSurvive scene changes (default: bound to the scene in which it was created and cancelled on change)
contextthis for fn
onCancelCalled when removed by a scene change, an error or clear()

Also: frame (counter), count(), clear(), update() (called automatically).

MF.Tween

PitfallDurations are frames (60 = 1 s). Tweens pause with the game window and are cancelled on scene change unless persistent. More

Animates numeric properties. Durations are in frames (60 = 1 second). Property names may be paths ("scale.x"). Tweens are awaitable: they resolve true on completion and false when stopped or cancelled by a scene change.

await MF.Tween.to(sprite, { x: 400, opacity: 0 }, { duration: 30, easing: "easeOutQuad" });

MF.Tween.from(window, { y: -200 }, { duration: 20, easing: "easeOutBack" });
MF.Tween.fromTo(sprite, { "scale.x": 0 }, { "scale.x": 1 }, { duration: 15 });
MF.Tween.value(0, 100, { duration: 60 }, v => gauge.setRate(v / 100));

const pulse = MF.Tween.to(icon, { opacity: 128 }, { duration: 20, repeat: -1, yoyo: true });
pulse.stop();

Options

OptionDefaultDescription
duration30Frames per cycle
delay0Frames before start (start values are read after the delay)
easing"linear"Name from MF.Math.easing
repeat0Extra cycles; -1 = infinite
yoyofalseReverse direction every cycle
roundfalseRound values (pixel-perfect positions)
group—Name for stopAll(group)
persistentfalseSurvive scene changes
onStart, onUpdate(progress), onRepeat(cycle), onComplete—Callbacks (this = tween)

Instance

MemberDescription
then(), promiseAwaitable
pause(), resume(), isPaused()Pause
stop()Stop at current values (resolves false)
finish()Jump to the end values and complete
isActive()Still running

Static

FunctionDescription
to(target, props, options?)Animate from current values to props
from(target, props, options?)Animate from props to current values; start values are applied immediately
fromTo(target, from, to, options?)Explicit start and end values
value(from, to, options?, onValue(v, progress))Animate a plain number
killTweensOf(target, finish = false)Stop all tweens of a target (optionally jump to their end)
isTweening(target)Any active tween on the target
stopAll(group?)Stop all tweens, or all tweens of a group
count()Number of active tweens

Non-numeric properties are skipped with a warning. Tweens of destroyed PIXI objects stop automatically.

Sequences

async function intro(sprite) {
    await MF.Tween.to(sprite, { opacity: 255 }, { duration: 20 });
    await MF.Timer.wait(30);
    await Promise.all([
        MF.Tween.to(sprite, { x: 200 }, { duration: 30 }),
        MF.Tween.to(sprite, { "scale.x": 2, "scale.y": 2 }, { duration: 30 })
    ]);
}

MF.Timer

FunctionDescription
wait(frames, { persistent? })Promise → true after n frames, false if cancelled by a scene change
after(frames, fn, { persistent? })Call once → { cancel(), isActive() }
every(frames, fn(count), { times?, persistent? })Repeat; return false from fn to stop → { cancel(), isActive() }
until(predicate, { timeout?, persistent? })Promise → true when predicate() is truthy (checked each frame); false on timeout (frames) or scene change

MF.Queue

Runs tasks one after another. A task is a function returning a value, a Promise or a Tween.

const queue = new MF.Queue();
queue.add(() => MF.Tween.to(actorSprite, { x: 300 }, { duration: 40 }), "walk");
queue.add(() => MF.Timer.wait(20));
queue.add(() => $gameScreen.startFlash([255, 255, 255, 170], 30));
queue.on("empty", () => console.log("cut-scene finished"));
MemberDescription
new MF.Queue({ paused? })Extends EventEmitter
add(fn, label?)Promise of the result. A failing task is logged, emits "error", rejects its promise, and the queue continues. The promise is pre-handled, so fire-and-forget calls never trigger MZ's unhandled-rejection error screen
pause(), resume()Pause before the next task
clear()Drop pending tasks (they resolve undefined); the running task finishes
size, isRunning()State
events empty, error (error, label)

MF.Layout experimental

Applies Units + Anchor layout to display objects, so plugins don't call Anchor.toAbsolute by hand in every window.

Layout spec

FieldDescription
x, yOffset from the container's anchor point (units)
width, heightUnits or "auto"; omitted → natural size
anchorOne of the nine anchors (default top-left)
minWidth, maxWidth, minHeight, maxHeightLimits (units, relative to container)
rowsWindows: rows for height: "auto"
fitSprites: none (default), stretch, contain, cover

Container

A layout is resolved against, in order: an explicit container (setLayoutContainer), the parent's layoutInnerSize(), a window's inner area (for objects added with addInnerChild), a parent window's size, or the UI area Graphics.boxWidth × boxHeight. Objects re-layout automatically when added to a parent, and a parent refreshes its laid-out children.

Functions

FunctionDescription
resolve(spec, container?, natural?, auto?)Pure calculation → { x, y, width, height }
toSpec(rect, container?, { anchor, relative })Inverse of resolve; relative: true stores percentages. For editors: after dragging, convert the rect back into a spec
containerOf(obj), screenSize()Container detection
mixinWindow(proto)Adds layout to any Window_* class: resizes via move(), recreates contents and calls refresh() only when the size changed. "auto" height uses rows, or maxItems/maxCols for selectable windows
mixinSprite(proto)Adds layout to any Sprite class: fit scaling, alignment inside the rect, respects sprite.anchor; natural size = bitmap frame
mixinContainer(proto)Sized group; natural size = container
mixin(proto, overwrite?)Generic (position only)
refreshChildren(obj), refreshTree(root)Re-layout

Instance methods (after mixin)

setLayout(spec, merge = false), getLayout(), hasLayout(), setLayoutContainer(sizeOrFn), layoutContainerSize(), layoutInnerSize(), layoutRect(), refreshLayout(); windows also layoutRows().

// Add layout to a built-in window class
MF.Layout.mixinWindow(Window_Gold.prototype);
scene._goldWindow.setLayout({ anchor: "bottom-right", x: -8, y: -8, width: 240, height: "auto", rows: 1 });

MF.UI experimental

Base classes with layout built in. They use MZ-style constructors, so they are subclassed the usual MZ way.

new MF.UI.Window(spec?, container?)

Window_Base with layout. Override refresh() to draw; it is called after every resize.

Pitfallrefresh() runs automatically only after a resize — call it after creation and when your data changes. More
function Window_QuestInfo() { this.initialize(...arguments); }
Window_QuestInfo.prototype = Object.create(MF.UI.Window.prototype);
Window_QuestInfo.prototype.constructor = Window_QuestInfo;

Window_QuestInfo.prototype.refresh = function() {
    MF.UI.Window.prototype.refresh.call(this);
    this.drawText(t("title"), 0, 0, this.innerWidth, "center");
};

const win = new Window_QuestInfo({ anchor: "bottom", y: -20, width: "60%", height: "auto", rows: 2 });
this.addWindow(win);
new MF.UI.Sprite(bitmap?, spec?)

Sprite with layout and fit modes. Re-layouts when the bitmap finishes loading.

const logo = new MF.UI.Sprite(ImageManager.loadPicture("Logo"),
    { anchor: "top", y: 40, width: "50%", height: 120, fit: "contain" });
new MF.UI.Container(spec?)

Invisible sized group (default: full container). Children resolve their layouts inside it.

const panel = new MF.UI.Container({ anchor: "center", width: 400, height: 300 });
panel.addChild(new MF.UI.Sprite(bitmap, { anchor: "top-right" }));

MF.Assets

Batch preloading for cut-scenes and heavy UI: parallel loading with a concurrency limit, progress reporting, and failures that are reported instead of crashing the game.

const task = MF.Assets.load([
    "picture:Cutscene_BG", "picture:Cutscene_Hero", "face:Actor1",
    { type: "se", name: "Thunder", static: true },
    { type: "bgm", name: "Theme3" },
    { type: "json", url: "data/Cutscene1.json" }
], {
    concurrency: 6,
    group: "cutscene1",
    onProgress: (p, item) => loadingBar.setRate(p)
});

const { ok, failed } = await task;
if (!ok) console.warn("missing:", failed.map(f => f.item.name));

Item types

ItemLoaded with
"picture:Name", { type: "picture", name }ImageManager.loadPicture. Also face, character, svActor, svEnemy, enemy, parallax, tileset, title1, title2, battleback1, battleback2, system
{ type: "image", folder, name }, { type: "image", url }, "img/…/x.png"ImageManager.loadBitmap / loadBitmapFromUrl
{ type: "bgm" | "bgs" | "me" | "se", name }AudioManager.createBuffer; se with static: true → AudioManager.loadStaticSe
{ type: "json", url }MF.FS.loadJson
{ type: "font", family, filename }FontManager.load
{ type: "custom", load: () => Promise }Your function
PitfallAudio caveat. RPG Maker creates a new buffer each time BGM/BGS/ME/SE is played, so preloading audio warms the file cache but playback still decodes again. Only static SE (static: true) are played from the preloaded buffer. More

Task

MemberDescription
await task / task.then{ ok, cancelled, results, failed: [{ item, error }] } — never rejects
progress, loaded, total, failedLive state (failed items count as processed in progress)
cancel(), isDone()Cancel stops starting new items
events progress (p, item), error (item, error), complete (result)EventEmitter
OptionDefaultDescription
concurrency6Parallel loads
timeout30000Per item, ms
group—Keep references to loaded objects under this name until release(group)
onProgress(progress, item, task)—Callback

Functions

FunctionDescription
load(items, options)→ task
bindScene(scene, task)The scene does not start (isReady() is false) until the task is done — call in create() for a loading phase without extra code
register(type, loader(item, timeout) → Promise)Custom asset type
isLoading()Any task running
release(group), retained(group)Group references
A failed image is removed from ImageManager's cache. Otherwise RPG Maker would later throw its load-error screen from ImageManager.isReady() in an unrelated scene.

MF.Registry 1.2.0

Named registries for extension points: button actions, element types, condition types of your own plugin. Other plugins add entries without patching your code.

const actions = MF.Registry.define("MF_QuestLog.actions", {
    validate: value => typeof value === "function"   // false → entry rejected with an error
});
const remove = actions.add("openLog", () => SceneManager.push(Scene_QuestLog), "MF_QuestLogAddon");
actions.get("openLog")();
actions.on("add", (id, value, owner) => { /* rebuild menus */ });
FunctionDescription
MF.Registry.define(name, { validate?, override? })Returns the registry, creating it on the first call (all plugins get the same object)
MF.Registry.get(name), has(name), names()Lookup without creating
add(id, value, owner)→ function that removes this entry. Replacing an existing id warns unless override: true
get(id, fallback), has(id), owner(id), remove(id)Access
ids(), list(), sizelist() → [{ id, value, owner }]
events add, removeThe registry is an EventEmitter

MF.Services 1.2.0

A plugin publishes an API under a name and a version; other plugins use it if it is present. No load-order or hard require is needed for optional integrations.

// MF_QuestLog
MF.Services.provide("quest", { start, isDone }, { version: "1.0.0", owner: "MF_QuestLog" });

// Another plugin — optional integration
const quest = MF.Services.use("quest", "1.0.0");   // null if missing or older
if (quest) quest.start("q1");

// Any load order
MF.Services.when("quest").then(api => api.start("q1"));
FunctionDescription
provide(name, api, { version, owner })Publishes (replacing warns); resolves pending when(); emits mf:serviceProvided (name, api)
use(name, minVersion?)API or null
require(requester, name, minVersion?)API or a readable error (for mandatory integrations)
has(name, minVersion?)Boolean
when(name, minVersion?)Promise; rejects if the provided version is too old
list()[{ name, version, owner }]

MF.Notetag 1.2.0

Notetag parsing with a cache (by note text). Tag names are case-insensitive, tags may repeat.

<Price: 120>          → 120
<Unique>              → true
<Element: fire, ice>  → "fire, ice" / ["fire", "ice"] with type "list"
<Description>         block: the text between the tags
Line 1
Line 2
</Description>
MF.Notetag.get($dataItems[5], "Price", { type: "number", default: 0 });
MF.Notetag.getAll(item, "Element", { type: "list" });          // [["fire", "ice"], ...]
MF.Notetag.get(event, "Loot", { type: "json", default: [] });  // Game_Event → event note
MF.Notetag.sum(battler, "CritBonus");                          // actor + class + equips + states
FunctionDescription
get(obj, tag, options)First value. obj: database object, note string, Game_Actor, Game_Enemy, Game_Event
getAll(obj, tag, options)All values of a repeated tag
has(obj, tag), parse(obj)parse → { lowerTag: [raw] } (raw is true or a string)
sources(battler)Actor, class, equips, states — or enemy, states
collect(battler, tag, options), sum(battler, tag, base)Values from all sources
clearCache()After changing note at runtime without changing its text reference — normally not needed
OptionDescription
typeauto (number / boolean / string), string (trimmed), text (as is), number, int, boolean (false/off/no/0 → false), list, numbers, json
defaultMissing tag or unparsable value
separatorFor list / numbers, default ","
schemaMF.Schema normalization; invalid → default + warning

MF.GameEvents 1.2.0

Standard game events on MF.Events.bus with the game: prefix, so plugins do not hook the same MZ methods. Change events (switches, variables, gold, items, level, states) do no extra work while nobody listens.

const off = MF.GameEvents.on("variableChanged", (id, value, old) => { ... });
MF.GameEvents.onScene(this, "goldChanged", this.refreshGold);   // this = scene; removed on terminate
MF.Events.bus.on("game:battleEnd", result => { ... });           // same event
EventArguments
newGame, saveLoaded—
switchChanged, variableChanged(id, value, old) — only when the value actually changes
goldChanged(value, old)
itemChanged(item, count, old) — items, weapons, armors
actorLevelChanged(actor, level, old)
stateAdded, stateRemoved(battler, stateId)
battleStart, turnStart, turnEnd—
battleEnd(result): 0 win, 1 escape, 2 lose
actionEnd(subject, action)
mapLoaded(mapId)
transfer(mapId, x, y)
eventStarted(gameEvent)
messageAdded(text) — once per message line

Functions: on, once, emit (your own game:* events), onScene(scene, name, fn, context = scene).

MF.Commands 1.2.0

Plugin commands with typed arguments. A handler that returns a Promise makes the event wait for it — no manual setWaitMode.

MF.Commands.register("MF_QuestLog", "StartQuest", async function(args) {
    // this — Game_Interpreter (null when called from code)
    await MF.Text.show(`Quest ${args.id} started`);
}, {
    id:    { type: "string", required: true },
    stage: { type: "integer", default: 0 }     // "3" → 3
});

await MF.Commands.call("MF_QuestLog", "StartQuest", { id: "q1" });
FunctionDescription
register(plugin, command, handler, schema?)Registers through PluginManager.registerCommand. schema — properties of the arguments object; invalid arguments are logged, defaults applied
call(plugin, command, args, interpreter?)→ Promise with the handler's result
has(plugin, command)Boolean
The wait is not saved: after loading a save the event continues without waiting. Errors of async handlers are logged and the event continues.

MF.Options 1.2.0

Adds entries to Scene_Options without patching Window_Options in every plugin. Values are MF.Config keys (registered automatically); the options window grows with the entries.

MF.Options.add({ key: "MF_QuestLog.tracker", label: "Quest tracker", type: "boolean", default: true });
MF.Options.add({ key: "MF_Hud.scale", label: "MF_Hud:options.scale", type: "number",
                 min: 50, max: 150, step: 10, default: 100, format: v => v + "%" });
MF.Options.add({ key: "MF_Hud.position", label: "HUD", type: "list",
                 values: [{ value: "top", label: "Top" }, { value: "bottom", label: "Bottom" }] });

MF.Config.get("MF_QuestLog.tracker");   // read the value anywhere
FieldDescription
keyConfig key (required)
labelString, I18n key "ns:key" or function
typeboolean | number | volume (0–100, step 20, like MZ) | list
defaultDefault value
min, max, step, wrapFor number / volume; wrap also for list. OK always wraps
valuesFor list: [{ value, label }] or plain values
format(value)Status text
visible()Hide the entry
afterMZ symbol to insert after ("commandRemember", "seVolume", …); default — at the end

Also: remove(key), list(), symbol(key) (command symbol "mf:" + key). Changes emit mf:configChanged; MZ saves the config when the options scene closes.

MF.Modifiers 1.2.0

Stacking modifiers instead of competing hooks. Every plugin adds a function; they run in priority order (lower first, then registration order). A non-number result is ignored for numeric stats; errors are logged and skipped.

const off = MF.Modifiers.add("param", (value, { battler, paramId }) =>
    paramId === 2 && battler.isStateAffected(10) ? value * 1.25 : value,
    { owner: "MF_Rage", priority: 100 });

MF.Modifiers.add("skillMpCost", (cost, { skill }) => skill.stypeId === 1 ? cost / 2 : cost);
const dmg = MF.Modifiers.apply("myDamage", base, { target });   // your own stat
StatctxResult
param{ battler, paramId }Rounded, clamped by paramMin/paramMax
xparam, sparam{ battler, xparamId } / { battler, sparamId }Rate as is
skillMpCost, skillTpCost{ battler, skill }Floored, ≥ 0
expGain, goldGain{ value }Battle rewards; rounded, ≥ 0

Functions: add(stat, fn, { priority, owner }) → remove function, apply(stat, value, ctx), has(stat), list(stat?). Without modifiers the built-in stats return the MZ value unchanged.

MF.Random 1.2.0

const rng = MF.Random.create("dungeon-42");   // same seed → same sequence
rng.int(1, 6); rng.chance(0.1); rng.pick(list); rng.shuffle(list);
rng.weighted([{ id: 1, weight: 5 }, { id: 2, weight: 1 }]);

MF.Random.stream("loot").int(1, 100);   // state stored in the save: no save-scumming
MF.Random.int(1, 6);                    // Math.random with the same helpers
FunctionDescription
create(seed)Independent generator (mulberry32). Seed: number or string
stream(name, seed?)Named generator whose state is saved in MF.Save (key MF_Core.random); new game → new state (from seed if given)
next(), float(min, max), int(min, max)int — both bounds inclusive; int(n) = 0…n
chance(p), pick(list), shuffle(list)shuffle returns a copy
weighted(list, weight = "weight")Property name or function; zero/negative weights are skipped
state, seed(seed)Read/restore the generator state
hash(value)String → 32-bit seed

Versioning policy

MF_Core follows Semantic Versioning. Several plugins are developed against it in parallel, so a silent change to any public function breaks all of them at once. The rules below are mandatory.

Version partAllowed changes
PATCH 1.2.xBug fixes only. No API changes.
MINOR 1.x.0New API; backward-compatible behavior changes; deprecations; changes of experimental APIs.
MAJOR x.0.0Removal of previously deprecated API; incompatible changes.

Deprecation lifecycle

Like PIXI, MF_Core never removes or incompatibly changes an API in one step:

  1. Replace. The new API is added. The old name keeps working and redirects to the new implementation through MF.Deprecation, printing a one-time warning.
  2. Announce. The changelog lists the old API under Deprecated with the since and removeIn versions.
  3. Remove. Only in the announced MAJOR release, listed under Removed ⚠ BREAKING. At least one MINOR release separates steps 1 and 3.
// Hypothetical MF_Core 1.1.0: Utils.oldName renamed to Utils.newName
MF.Utils.newName = function(...) { ... };
MF.Deprecation.method(MF.Utils, "oldName", {
    id: "MF.Utils.oldName", since: "1.1.0", removeIn: "2.0.0",
    replacement: "MF.Utils.newName", fn: MF.Utils.newName
});
// Console (once): [MF_Core] MF.Utils.oldName is deprecated since MF_Core 1.1.0
//                 and will be removed in 2.0.0. Use MF.Utils.newName instead.

Dependent plugins

Changelog

Every public API change is recorded in CHANGELOG.md in the same commit, tagged Added, Changed, Deprecated, Removed ⚠, Breaking ⚠, Experimental change or Fixed.

Compatibility of 1.2.0 with 1.1.0

No function was removed or renamed; every 1.1.0 signature works as before. Changes of existing behavior:

AreaBeforeNowImpact
Utils.parseParams / Core.parameters"007" → 7"007" stays a string; "0", "10", "0.5", "-3" are still numbersOnly parameters with leading zeros. A schema with type: "number" still converts them
Script.run / compileCode with ; or a line break ran as statements and returned undefinedTried as one expression first, then as statementsActions: same effect. Conditions like "a &&\n b" now work; "x = 5;" now returns 5 instead of undefined
History.isDirty, nested beginBatchWrong after undo + edit; inner batch closed the outer oneCorrectBug fix
Units.resolveWarning every call for an invalid expressionOnce per errorLess console output
Text.wrapFroze on a character wider than the lineSuch a character gets its own lineBug fix
Input.modifiers / isShortcutFrom tracked keys onlyFrom key-event flagsBug fix
Double loadingHooks installed twiceSecond copy ignored with a warning—

New MZ hooks (all through MF.Hook, calling the original): Game_Switches/Game_Variables.setValue, Game_Party.gainGold/gainItem, Game_Actor.changeExp, Game_Battler.addState/removeState, Game_BattlerBase.param/xparam/sparam/skillMpCost/skillTpCost, BattleManager.startBattle/startTurn/endTurn/endAction/endBattle/makeRewards, Game_Map.setup, Game_Player.performTransfer, Game_Event.start, Game_Message.add, Game_Interpreter.updateWait/clear, Window_Options, Scene_Options.maxCommands, Scene_Base.terminate, DataManager.setupNewGame. Without listeners, modifiers or options they return the original results. A plugin below MF_Core that replaces one of these methods without calling the original disables only the matching new feature.

Save files: a new key MF_Core.random appears in contents.mf. Saves are readable by 1.1.0 (unknown keys are kept as orphans), and 1.1.0 saves load in 1.2.0.

1.2.1 adds two engine performance hooks (through MF.Hook, calling the original, results unchanged): ColorManager.textColor (cached per loaded windowskin; a plugin that draws into the windowskin bitmap at runtime should assign a new bitmap to ColorManager._windowskin) and Bitmap.prototype.drawText (missing align → "start").

Bus events

Emitted on MF.Events.bus:

EventArgumentsWhen
mf:dataLoaded(globalName, value)A data file was loaded (or its default was applied)
mf:dataError(globalName, { code, message })A file is invalid, or a required file failed
mf:scriptBlocked({ type, code, message, count })A script was skipped because scripts are disabled (once per snippet)
mf:scriptError({ type, code, message, count })A script threw (once per snippet)
mf:deprecated({ id, since, removeIn, replacement, owner, count })A deprecated API was used (once per id)
mf:saveLoaded—MF.Save data was read from a save file
mf:saveError(key, error)Migration or onLoad of save data failed (raw data kept)
mf:configLoaded—config.rmmzsave was applied
mf:configChanged(key, value, old)A config value changed
mf:localeChanged(locale, old)The current locale changed
mf:inputDeviceChanged(device, old)The player switched between keyboard, mouse, touch and gamepad
mf:serviceProvided(name, api)A service was published (1.2.0)
game:*see MF.GameEventsGame events (1.2.0)

Error handling summary

AreaPolicy
HooksPropagate by default; safe isolates the extension only
Event listenersCaught and logged per call
Conditionsfalse, logged once per unique condition
ScriptsFallback value, logged once per snippet, bus event, issues()
UnitsFallback value, warning once per error
NotetagsUnparsable value → default; schema errors warned
CommandsInvalid arguments warned, defaults applied; sync errors propagate (error screen), async errors logged
Modifiers, service waitersFailing modifier logged and skipped; when() rejects on a too old version
Schema / plugin parametersError list; Core.parameters logs one warning with all fields; assert throws
Data filesSee MF.Data error handling
Save dataRaw data kept on migration errors, mf:saveError
Config valuesInvalid stored value → default, warning
I18nMissing key → fallback/key, warned once
Ticker / Tween / Timer callbacksLogged; a throwing ticker entry is removed. Tween callbacks are isolated
Queue tasksLogged, "error" event, promise rejects (pre-handled), queue continues
Text codesFailing macro → empty string, failing escape → skipped; logged once per code+argument
Drag callbacksLogged, drag continues
AssetsTask never rejects; failures in failed, logged, "error" event; failed images evicted from cache
Documentssave() refuses invalid data and overwriting unparsable files; returns a reason instead of throwing
File writesReturn false, error logged; atomic, optional backup
Deprecated APIKeeps working; warned once

Changelog

The full changelog with Breaking / Deprecated markers is in CHANGELOG.md. Summary:

1.2.1 — 2026-09-26

1.2.0 — 2026-09-25

1.1.0 — 2026-09-23

1.0.0 — 2026-09-23