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.
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).
| Module | Purpose |
|---|---|
MF.Core | Plugin registration, dependency and version checks, parameter parsing |
MF.Log | Prefixed console logging |
MF.Utils | Type checks, clone, merge, diff, path access, ids, versions |
MF.Math | Numbers, snapping, rectangles, easing |
MF.Units | Size expressions: "240", "50%", "100%-240", "auto", functions and conditions (1.1.0) |
MF.Anchor | Nine anchor points and coordinate conversion |
MF.Color | Color parsing and conversion |
MF.Hook | Method overriding: alias / before / after |
MF.Events | EventEmitter and the global bus |
MF.Deprecation | Deprecation warnings and API redirects |
MF.Schema | Validation and normalization of structured data and plugin parameters |
MF.Condition | Declarative conditions (switches, variables, items, …) |
MF.Script | Access-controlled execution of JS from data |
MF.FS | File access (NW.js), JSON loading, downloads |
MF.Data | Extra data/*.json files loaded with the database |
MF.Save | Plugin state inside save files |
MF.Config | Plugin settings in config.rmmzsave |
MF.Migration | Versioned data format migrations |
MF.History | Undo/Redo stack |
MF.Document experimental | Editable JSON document: undo/redo + validation + safe save |
MF.I18n | Localization with plural forms |
MF.Text | Custom escape codes, message/choice Promises, word wrap |
MF.Input | Actions, raw keys, shortcuts, pointer, drag-and-drop experimental, devices |
MF.Ticker | Per-frame update runner |
MF.Tween | Awaitable property animation |
MF.Timer | Frame-based waits, delays, intervals |
MF.Queue | Sequential async task queue |
MF.Layout experimental | Units + Anchor layout for any display object |
MF.UI experimental | Window / Sprite / Container with built-in layout |
MF.Assets | Batch preloading with progress and safe failures |
MF.Registry 1.2.0 | Named extension registries (actions, element types, …) |
MF.Services 1.2.0 | Versioned APIs between plugins without hard dependencies |
MF.Notetag 1.2.0 | Typed notetags, blocks, values collected from a battler |
MF.GameEvents 1.2.0 | game:* events: switches, variables, gold, items, battle, map |
MF.Commands 1.2.0 | Plugin commands with typed arguments and async wait |
MF.Options 1.2.0 | Shared Scene_Options entries stored in MF.Config |
MF.Modifiers 1.2.0 | Stacking stat modifiers with priorities |
MF.Random 1.2.0 | Seeded 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
- MF_Core must be above every MF_* pluginOtherwise
MFis undefined when the plugin loads. Add@base MF_Coreand@orderAfter MF_Coreso the Plugin Manager warns about the order.See: Installation, Plugin template - Always state the MF_Core version you wrote against
register(name, v, { requires: { MF_Core: "1.0.0" } })and the same in@help. Without it you cannot tell later which plugins were written for which API; a missing version logs a warning.See: Versioning policy, MF.Core - Experimental APIs may change in a MINOR release
MF.Layout,MF.UI,MF.DocumentandMF.Input.draggablecan change in 1.x — always with a changelog entry. Read the changelog before updating MF_Core.See: Versioning policy - Errors in hooks are not swallowedAn exception in
MF.Hook.alias/before/aftershows the error screen, like any MZ plugin. Use{ safe: true }only for cosmetic additions — it hides errors of your function.See: MF.Hook › safe mode
Text and messages
- Backslashes are doubled in JavaScript stringsIn code:
"\\C[2]Hi\\C[0]". In the event editor you type\C[2]Hi\C[0]. A single backslash in JS silently turns\CintoC.See: MF.Text - Escape codes also run when text is only measuredRPG Maker processes codes in
textSizeEx(choice widths, word wrap) without drawing. Custom escapes with side effects (sounds, shakes, waits) must checkctx.drawing, and message-only effectsctx.isMessage. Otherwise a sound plays when the choice window is sized. \FACEdoes not move the textChanging the face mid-message keeps the text position. Start the message with a face (or reserve space) if you switch faces later.- Code names: letters only, some are reservedRPG Maker parses codes as
[A-Z]+, so\I18Nwould be read as\I.V N P G C I PX PY FSare reserved. Parameters cannot contain]. MF.Text.showneeds a message windowWorks on the map and in battle. In your own scene there is noWindow_Message— the Promise waits forever (until the scene changes, then resolvesfalse).- Script conditions are off by defaultWith Allow Scripts disabled,
{ script: "…" }is alwaysfalse. A warning is logged once per snippet — check the console if a condition "never works".See: MF.Script, Parameters
Time and animation
- Durations are frames, bound to the sceneWork created in a scene's constructor or
initialize()belongs to the previous scene (it is still current) — pass{ scene: this }(1.2.0) or start it increate().duration: 60= one second at 60 fps. Tweens, timers andTimer.untilpause while the game window is inactive and are cancelled on scene change (Promises resolvefalse). Usepersistent: trueto survive scene changes. Assets.bindScenemust be called before the scene startsCall it increate(). Once the scene has started,isReady()is no longer consulted.See: MF.Assets- Preloaded audio is decoded again on playbackRPG Maker creates a new buffer for every BGM/BGS/ME/SE playback. Preloading only warms the file cache — except static SE (
static: true).See: MF.Assets
Data, saves and files
- Save data must be plain JSON-like dataObjects, arrays, strings, numbers, booleans. Class instances survive only if their constructor is a global; sprites, bitmaps and functions never do.See: MF.Save
- Module-level variables are not savedA
let quests = {}inside your plugin is lost on load and shared between save slots. UseMF.Save(per save) orMF.Config(global). - An invalid optional data file becomes the defaultThe game keeps running, but saving that default from an editor would overwrite the user's broken file. Check
MF.Data.status()— or useMF.Document, which refuses such saves.See: MF.Data, MF.Document Utils.diffcannot express removed keysRemoving a key from the target is not visible in the diff. Store explicit values such asvisible: false.See: MF.Utils- Invalid size expressions fall back silently-ish
"50% +"resolves to the fallback (0 by default) with a console warning. Validate user input withMF.Units.isValidor aunitsschema.
Input and display
- RPG Maker already uses many keysDefault bindings include Tab, Enter, Shift, Ctrl, Alt, Esc, Space, PageUp/Down, arrows, Insert, Q, W, X, Z, numpad and F9.
MF.Input.bindskips those with a warning unlessoverride: true.See: MF.Input › Actions - Shortcuts are ignored while typing in HTML fields
isShortcutreturnsfalsewhen aninput/textareahas focus (editor inspectors), unless{ whileTyping: true }. - A consumed pointer is invisible to the game for that frameWhile dragging (or after
consumePointer())TouchInput.*returnsfalse— windows under the pointer don't react. Read raw values withMF.Input.pointer().See: Drag-and-drop MF.UI.Windowredraws only after a resizerefresh()is called automatically when the size changes. Call it yourself after creation and when your data changes.See: MF.UI
Installation
- Copy
MF_Core.jstojs/plugins/. - Enable it in the Plugin Manager.
- 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
| Parameter | Default | Description |
|---|---|---|
debug (Debug Mode) | false | Prints MF.Log.debug messages to the console. |
allowScript (Allow Scripts) | false | Allows 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
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" } });
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.
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.
| Property | Description |
|---|---|
MF.Core.VERSION | Version 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
| Function | Returns 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
| Function | Description |
|---|---|
isTest() | Test play (test option) |
isBattleTest() | Battle test |
isEventTest() | Event test |
isNwjs() | Running in NW.js (desktop) |
Data
Deep copy of plain data. Class instances are returned by reference.
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 } }
Deep equality of plain data (NaN equals NaN).
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 } }
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. MorePath access
Paths are dot strings ("a.b.0.c") or arrays (["a", "b", 0, "c"]).
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
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 }.
| Function | Description |
|---|---|
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
| Member | Description |
|---|---|
easing | Object 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) |
EPSILON | 1e-6, default tolerance of approx |
MF.Units
Parses size and position expressions without eval. Compiled expressions are cached.
| Syntax | Meaning |
|---|---|
240, "240", "240px" | Pixels |
"50%" | Percent of base |
+ - * /, parentheses, unary minus | Arithmetic: "100%-240", "(100%-20)/2" |
"auto" | Resolved by the auto option |
| Option | Default | Description |
|---|---|---|
auto | — | Number or function used for "auto" |
fallback | 0 | Used for empty/invalid values (a warning is logged for invalid ones) |
round | true | Round 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
| Function | Description |
|---|---|
isAuto(v) | v === "auto" |
AUTO | The 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.0 | Frozen 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
| Group | Syntax |
|---|---|
| Operators | ^ (power), < > <= >= == !=, && || ! (result 1 / 0), cond ? a : b |
| Math | min, 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) |
| Angles | radians: 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) |
| Constants | pi / 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.
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 }
| Member | Description |
|---|---|
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? }.
| Function | Result |
|---|---|
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.
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");
Calls fn(...args) first. Returning false skips the original.
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 });
safe only for optional, cosmetic additions — it hides errors of your function. alias has no safe mode because the wrapper controls the original call. MoreInstalled hooks, for compatibility debugging.
MF.Events
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.
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).
Redirects get/set of obj[oldName] to obj[newName].
Manual report, e.g. for a deprecated option value or data format.
info field | Description |
|---|---|
id | Display name, e.g. "MF.Utils.oldName" (default: oldName) |
since | Version in which the API was deprecated |
removeIn | Version in which it will be removed |
replacement | What to use instead |
owner | Plugin name (default MF_Core) — other MF_* plugins can use the same tool |
note | Extra 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
Validation only; the value is not changed.
Validation plus defaults and coercion of string values ("12" → 12, "true" → true, JSON strings → objects). The input is not mutated.
Like normalize, but throws an Error listing all problems.
Schema fields
| Field | Applies to | Description |
|---|---|---|
type | all | any (default), number, integer, string, boolean, array, object, units, color, anchor, condition, or a registered type |
default | all | Value or factory function; applied by normalize when the value is missing |
required | object properties | Missing value is an error |
nullable | all | null is allowed |
enum | all | Allowed values (deep equality) |
oneOf | all | Alternative schemas; the first that matches wins |
validate | all | fn(value) → true | "error message" |
coerce | all | false disables string coercion in normalize |
min, max | number, integer | Range |
minLength, maxLength, pattern | string | Length, RegExp or string pattern |
items, minItems, maxItems | array | Item schema and size |
properties, additionalProperties | object | Property schemas; unknown keys: true (kept, default), false (error) or a schema |
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}`);
});
MF.Condition
null, undefined and true are true; an array is treated as all.
| Condition | True 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).
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 });
Switch and variable ids a condition depends on — lets UIs refresh on change instead of every frame.
MF.Script
false until Allow Scripts is on (warning logged once). Morecode 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:
- a warning with the code and the parameter name is logged once per snippet;
- the bus event
mf:scriptBlockedis emitted once per snippet; issues()lists all blocked/failed snippets with occurrence counts.
Runtime errors behave the same way (mf:scriptError, logged once).
| Function | Description |
|---|---|
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) |
MF.FS
Relative paths are resolved from the project root (the folder containing index.html).
NW.js only
| Function | Description |
|---|---|
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
Rejected errors carry err.code:
| code | Meaning |
|---|---|
notFound | 404, empty file:// response or network error |
http | Other HTTP error status |
parse | File exists but contains invalid JSON |
Triggers a browser download.
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.
| Option | Default | Description |
|---|---|---|
optional | true | A 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
| Situation | Result |
|---|---|
| Optional, file missing | Default value, debug log only |
Optional, invalid JSON / HTTP error / onLoad threw | Default value, console error, status().error set, usedDefault = true, mf:dataError emitted |
| Required, any error | Standard MZ load error screen with Retry. The database is not reported as loaded until the file loads successfully |
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. Morestate: idle | pending | loaded | failed. error: { code, message } or null; codes are those of loadJson plus process (onLoad threw).
Reloads a file (e.g. after saving in the editor). Rejects on any error except "optional file not found"; the rejected error has code.
Throws a pending required-file error in the ["LoadError", url, retry] format expected by SceneManager. Called automatically from DataManager.isDatabaseLoaded.
MF.Save
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 };
Registers a save slot for a plugin (use the plugin name as key). Call at plugin load time.
| Option | Default | Description |
|---|---|---|
default | {} | Value (cloned) or factory. Used for a new game and for saves without this key |
version | 1 | Data format version written into the save |
migrate(data, fromVersion) | — | Called when the save has an older version |
mergeDefaults | true | Deep-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 |
| Method | Description |
|---|---|
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 call | MF.Save |
|---|---|
DataManager.createGameObjects (new game, battle/event test, before loading) | All keys reset to defaults |
DataManager.makeSaveContents | Data written to contents.mf |
DataManager.extractSaveContents | Data read, migrated, merged; mf:saveLoaded emitted |
- Disabled plugins: data of keys that are not registered is kept and written back, so temporarily disabling a plugin does not wipe its progress.
- Errors in
migrate/onLoadkeep the raw data (defaults would destroy it on the next save), log an error and emitmf:saveError. - Newer save (version above the registered one): warning, data kept as is.
- Store plain JSON-like data. Class instances survive only if their constructor is a global (normal MZ
JsonExrules).
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 });
| Method | Description |
|---|---|
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
Registers a step from fromVersion to fromVersion + 1. fn(data) may mutate and/or return the 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);
| Method | Description |
|---|---|
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.
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
| Method | Description |
|---|---|
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.
| Method | Description |
|---|---|
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 |
fallbackLocale | Property, default "en" |
\C[2], \V[1]) — they are left untouched for drawTextEx.MF.Text
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
| Code | Kind | Effect |
|---|---|---|
\TR[ns:key] | macro | I18n translation (may contain other codes) |
\SAVE[key:path] | macro | Value from MF.Save data, e.g. \SAVE[MF_QuestLog:stats.done] |
\W[n] | escape | Wait n frames (message window) |
\SE[name,volume,pitch,pan] | escape | Play a sound effect at this point of the text (message window) |
\FACE[name,index] | escape | Change the face mid-message; text pauses until it is loaded; \FACE[] removes it (message window) |
\SPD[n] | escape | n extra frames per character until the end of the message; \SPD[0] = normal; skipped by fast-forward (message window) |
\FACE does not move text: start the message with a face (or reserve space) if faces are switched later. MoreCustom 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.
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]"
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."
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. MoreMessages 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.");
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
bind skips them unless override: true. MoreMF.Input.bind("journal", { keys: ["j"], buttons: [8] }); // J key, gamepad "back"
if (Input.isTriggered("journal")) SceneManager.push(Scene_Journal);
if (MF.Input.isReleased("journal")) { … }
| Function | Description |
|---|---|
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();
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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
TouchInput.* returns false for the game; read raw values with MF.Input.pointer(). Morelet 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)
});
| Option | Default | Description |
|---|---|---|
threshold | 4 | Pixels before a press becomes a drag (otherwise onClick) |
priority | 0 | Overlapping targets: higher priority wins, then the most recently registered |
consume | true | Consume the pointer while dragging and on release |
persistent | false | Keep across scene changes (default: removed with the scene) |
hitTest(x, y, target) | bounds | Custom 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.
Devices
| Function | Description |
|---|---|
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.
Return false from fn to stop. A throwing callback is logged and removed.
| Option | Description |
|---|---|
persistent | Survive scene changes (default: bound to the scene in which it was created and cancelled on change) |
context | this for fn |
onCancel | Called when removed by a scene change, an error or clear() |
Also: frame (counter), count(), clear(), update() (called automatically).
MF.Tween
persistent. MoreAnimates 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
| Option | Default | Description |
|---|---|---|
duration | 30 | Frames per cycle |
delay | 0 | Frames before start (start values are read after the delay) |
easing | "linear" | Name from MF.Math.easing |
repeat | 0 | Extra cycles; -1 = infinite |
yoyo | false | Reverse direction every cycle |
round | false | Round values (pixel-perfect positions) |
group | — | Name for stopAll(group) |
persistent | false | Survive scene changes |
onStart, onUpdate(progress), onRepeat(cycle), onComplete | — | Callbacks (this = tween) |
Instance
| Member | Description |
|---|---|
then(), promise | Awaitable |
pause(), resume(), isPaused() | Pause |
stop() | Stop at current values (resolves false) |
finish() | Jump to the end values and complete |
isActive() | Still running |
Static
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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"));
| Member | Description |
|---|---|
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
| Field | Description |
|---|---|
x, y | Offset from the container's anchor point (units) |
width, height | Units or "auto"; omitted → natural size |
anchor | One of the nine anchors (default top-left) |
minWidth, maxWidth, minHeight, maxHeight | Limits (units, relative to container) |
rows | Windows: rows for height: "auto" |
fit | Sprites: 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
| Function | Description |
|---|---|
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.
Window_Base with layout. Override refresh() to draw; it is called after every resize.
refresh() runs automatically only after a resize — call it after creation and when your data changes. Morefunction 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);
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" });
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
| Item | Loaded 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 |
static: true) are played from the preloaded buffer. MoreTask
| Member | Description |
|---|---|
await task / task.then | { ok, cancelled, results, failed: [{ item, error }] } — never rejects |
progress, loaded, total, failed | Live 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 |
| Option | Default | Description |
|---|---|---|
concurrency | 6 | Parallel loads |
timeout | 30000 | Per item, ms |
group | — | Keep references to loaded objects under this name until release(group) |
onProgress(progress, item, task) | — | Callback |
Functions
| Function | Description |
|---|---|
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 |
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 */ });
| Function | Description |
|---|---|
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(), size | list() → [{ id, value, owner }] |
events add, remove | The 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"));
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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 |
| Option | Description |
|---|---|
type | auto (number / boolean / string), string (trimmed), text (as is), number, int, boolean (false/off/no/0 → false), list, numbers, json |
default | Missing tag or unparsable value |
separator | For list / numbers, default "," |
schema | MF.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
| Event | Arguments |
|---|---|
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" });
| Function | Description |
|---|---|
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 |
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
| Field | Description |
|---|---|
key | Config key (required) |
label | String, I18n key "ns:key" or function |
type | boolean | number | volume (0–100, step 20, like MZ) | list |
default | Default value |
min, max, step, wrap | For number / volume; wrap also for list. OK always wraps |
values | For list: [{ value, label }] or plain values |
format(value) | Status text |
visible() | Hide the entry |
after | MZ 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
| Stat | ctx | Result |
|---|---|---|
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
| Function | Description |
|---|---|
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 part | Allowed changes |
|---|---|
PATCH 1.2.x | Bug fixes only. No API changes. |
MINOR 1.x.0 | New API; backward-compatible behavior changes; deprecations; changes of experimental APIs. |
MAJOR x.0.0 | Removal of previously deprecated API; incompatible changes. |
Deprecation lifecycle
Like PIXI, MF_Core never removes or incompatibly changes an API in one step:
- 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. - Announce. The changelog lists the old API under Deprecated with the
sinceandremoveInversions. - 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
- Declare the minimum MF_Core version the plugin was written against:
register(name, version, { requires: { MF_Core: "1.0.0" } }). A barerequire("…", "MF_Core")without a version logs a warning. - State the same version in
@help:Requires: MF_Core 1.0.0+ (written against 1.0.0). - If the installed MF_Core has a higher MAJOR version than required, a one-time compatibility warning is logged.
- During development, check
MF.Deprecation.list()— it shows every deprecated API the running plugins still use.
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:
| Area | Before | Now | Impact |
|---|---|---|---|
Utils.parseParams / Core.parameters | "007" → 7 | "007" stays a string; "0", "10", "0.5", "-3" are still numbers | Only parameters with leading zeros. A schema with type: "number" still converts them |
Script.run / compile | Code with ; or a line break ran as statements and returned undefined | Tried as one expression first, then as statements | Actions: same effect. Conditions like "a &&\n b" now work; "x = 5;" now returns 5 instead of undefined |
History.isDirty, nested beginBatch | Wrong after undo + edit; inner batch closed the outer one | Correct | Bug fix |
Units.resolve | Warning every call for an invalid expression | Once per error | Less console output |
Text.wrap | Froze on a character wider than the line | Such a character gets its own line | Bug fix |
Input.modifiers / isShortcut | From tracked keys only | From key-event flags | Bug fix |
| Double loading | Hooks installed twice | Second 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:
| Event | Arguments | When |
|---|---|---|
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.GameEvents | Game events (1.2.0) |
Error handling summary
| Area | Policy |
|---|---|
| Hooks | Propagate by default; safe isolates the extension only |
| Event listeners | Caught and logged per call |
| Conditions | false, logged once per unique condition |
| Scripts | Fallback value, logged once per snippet, bus event, issues() |
| Units | Fallback value, warning once per error |
| Notetags | Unparsable value → default; schema errors warned |
| Commands | Invalid arguments warned, defaults applied; sync errors propagate (error screen), async errors logged |
| Modifiers, service waiters | Failing modifier logged and skipped; when() rejects on a too old version |
| Schema / plugin parameters | Error list; Core.parameters logs one warning with all fields; assert throws |
| Data files | See MF.Data error handling |
| Save data | Raw data kept on migration errors, mf:saveError |
| Config values | Invalid stored value → default, warning |
| I18n | Missing key → fallback/key, warned once |
| Ticker / Tween / Timer callbacks | Logged; a throwing ticker entry is removed. Tween callbacks are isolated |
| Queue tasks | Logged, "error" event, promise rejects (pre-handled), queue continues |
| Text codes | Failing macro → empty string, failing escape → skipped; logged once per code+argument |
| Drag callbacks | Logged, drag continues |
| Assets | Task never rejects; failures in failed, logged, "error" event; failed images evicted from cache |
| Documents | save() refuses invalid data and overwriting unparsable files; returns a reason instead of throwing |
| File writes | Return false, error logged; atomic, optional backup |
| Deprecated API | Keeps working; warned once |
Changelog
The full changelog with Breaking / Deprecated markers is in CHANGELOG.md. Summary:
1.2.1 — 2026-09-26
- Fixed: performance —
ColorManager.textColorresults cached per windowskin (nogetImageDataper\C[n]);Bitmap.drawTextwithoutalignuses"start"instead of the invalidundefined(noCanvasTextAlignwarnings). Rendering unchanged.
1.2.0 — 2026-09-25
- Added:
MF.Registry,MF.Services,MF.Notetag,MF.GameEvents,MF.Commands,MF.Options,MF.Modifiers,MF.Random;options.scenefor Ticker / Tween / Timer. - Changed:
parseParamskeeps leading-zero strings;Scriptevaluates multi-line code as an expression first. See compatibility. - Fixed:
Text.wrapfreeze and CRLF,Unitswarning spam,History.isDirtyand nested batches, shortcut modifiers, double loading.
1.1.0 — 2026-09-23
- Added:
MF.Unitsfunctions (min,max,clamp,lerp,angle,atan2d,sind, …), constants,^, comparisons,&& || !,? :;Units.FUNCTIONS,Units.CONSTANTS,Units.arity. Changed: clearerMF.Unitserror messages. Backward compatible.
1.0.0 — 2026-09-23
- Initial release. Foundation:
Core,Log,Utils,Math,Units,Anchor,Color,Hook,Events,Deprecation,Schema. Game logic:Condition,Script. Storage:FS,Data,Save,Config,Migration,History,Documentexperimental. Text:I18n,Text. Input:Input(drag-and-drop experimental). Time:Ticker,Tween,Timer,Queue. Display and resources:Layoutexperimental,UIexperimental,Assets.