Skip to content

Instantly share code, notes, and snippets.

@f-space
Last active Sep 12, 2020
Embed
What would you like to do?
An RPG Maker MZ plugin that replaces arguments of plugin commands.
/*:
* @target MZ
* @plugindesc
* 🔧 プラグインコマンドの引数を置換します。
* Version: 0.1.0
* @author F_
* @url https://github.com/f-space/rmmz-plugins
*
* @base Fs
* @orderAfter Fs
*
* @help
*
* 任意のプラグインコマンドの引数を変数やスイッチの値で置き換えます。
*
*
* ■ 必要プラグイン
*
* このプラグインの動作には次のプラグインの導入が必要です。
*
* ・Fs
*
*
* ■ 対象引数の設定方法
*
* ○ 常に置換する場合
*
* プラグインパラメータに対象プラグインのコマンド名を入力し、
* 置換対象の引数を表すパスを指定します。
*
* ○ 一度だけ置換する場合
*
* 対象プラグインコマンドの直前に『引数の置換』コマンドを挿入し、
* 置換対象の引数を表すパスを指定します。
*
* ○ パスの指定方法
*
* 引数ごとに設定されたIDを指定します。
* 引数のIDは入力ダイアログ中の引数名に続く()内に記述されている文字列です。
*
* 引数に構造がある場合、最初に選択した引数のIDから順に"/"区切りで指定します。
* 複数指定可能な引数など、任意のIDに一致させたい場合には、
* IDの代わりに"*"と記述します。
*
* 例1)『アクターID(actorId)』を指定する場合
* actorId
*
* 例2)『表示位置(Position)』の『x座標(x)』を指定する場合
* Position/x
*
* 例3)『画像一覧(image_list)』内のすべての『ファイル(name)』を指定する場合
* image_list/*‌/name
*
*
* ■ 置換文字列の設定
*
* プラグインコマンドの引数を置き換えるには、上記の対象設定を行った上で、
* 引数として置換用の文字列を設定します。
* 置換文字列を設定するには、入力ダイアログで「テキスト」タブを選択します。
*
* 置換文字列中には次の制御文字を指定することができます。
*
* \V[n]: 変数番号[n]の値で置き換えられます。
* \S[n]: スイッチ番号[n]の値で置き換えられます。
* \S[n|on/off]:
* スイッチ番号[n]がONのときonの文字列に、
* OFFのときoffの文字列に置き換えられます。
*
* 単に変数やスイッチの値で置換する場合には、これらの制御文字のみを記述します。
*
*
* ---
* Copyright (c) 2020 F_
* Released under the MIT license
* https://github.com/f-space/rmmz-plugins/blob/master/LICENSE
*
* @param Targets
* @type struct<Plugin>[]
* @default []
* @text 対象プラグイン
* @desc 置換対象のコマンドを提供するプラグイン。
*
* @command replace
* @text 引数の置換
* @desc 引数の値を指定値に置き換えます。
*
* @arg path
* @type string
* @text 対象引数
* @desc 置換対象の引数を示すパス。
*/
/*~struct~Plugin:
* @param name
* @type string
* @text プラグイン名
* @desc プラグインの名前。
*
* @param commands
* @type struct<Command>[]
* @default []
* @text 対象コマンド
* @desc 置換対象のコマンド
*/
/*~struct~Command:
* @param name
* @type string
* @text コマンド名
* @desc コマンドの名前。
*
* @param paths
* @type string[]
* @default []
* @text 対象引数
* @desc 置換対象の引数を示すパスの一覧。
*/
"use strict";
{
const { P, Z } = Fs;
const COMMAND_REPLACE = "replace";
const TERMINAL_SYMBOL = Symbol("terminal");
const pathParser = P.map(P.string, s => s.split("/"));
const pathTreeParser = P.map(P.make([pathParser]), paths => paths.reduce((tree, path) => {
const rec = (tree, path) => {
const [first, ...rest] = path;
const value = first === TERMINAL_SYMBOL ? null : rec(tree[first] ?? {}, rest);
return { ...tree, [first]: value };
};
return rec(tree, [...path, TERMINAL_SYMBOL]);
}, {}));
const PLUGIN_NAME = Z.pluginName();
const PARAMS = PluginManager.parameters(PLUGIN_NAME);
const TARGETS = P.parse(PARAMS["Targets"], P.map(
P.make([
{
name: P.string,
commands: [
{
name: P.string,
paths: pathTreeParser,
}
],
}
]),
params => new Map(params.flatMap(p => p.commands.map(c => [`${p.name}:${c.name}`, c.paths])))
));
const mapProperties = (obj, fn) => {
if (Array.isArray(obj)) {
return obj.map((value, key) => fn(key, value));
} else {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(key, value)]));
}
};
const mergePaths = (a, b) => {
if (a === undefined) return b;
if (b === undefined) return a;
if (a[TERMINAL_SYMBOL] !== undefined) return a;
if (b[TERMINAL_SYMBOL] !== undefined) return b;
const keys = [...new Set([Object.keys(a), Object.keys(b)])];
return Object.fromEntries(keys.map(key => [key, mergePaths(a[key], b[key])]));
};
const replaceAll = (args, paths) => {
return mapProperties(args, (key, value) => {
const rest = mergePaths(paths["*"], paths[key]);
if (rest !== undefined) {
if (rest[TERMINAL_SYMBOL] !== undefined) {
return evalValue(value);
} else {
return JSON.stringify(replaceAll(JSON.parse(value), rest));
}
} else {
return value;
}
});
};
const replace = (args, path) => {
const [first, ...rest] = path;
return mapProperties(args, (key, value) => {
if (first === "*" || first === key) {
if (rest.length === 0) {
return evalValue(value);
} else {
return JSON.stringify(replace(JSON.parse(value), rest));
}
} else {
return value;
}
});
};
const evalValue = value => value.replace(/\\([vs])\[([^\]]*)\]/gi, (_, ch, params) => {
switch (ch.toUpperCase()) {
case "V": return evalVariable(params);
case "S": return evalSwitch(params);
default: throw new Error("unreachable code");
}
});
const evalVariable = params => {
if (/^\d{1,4}$/.test(params)) {
const id = Number.parseInt(params, 10);
return String($gameVariables.value(id));
} else {
throw new Error(`Invalid format: \\V[${params}]`);
}
};
const evalSwitch = params => {
const match = params.match(/^(\d{1,4})(?:\|([^\/]*)\/([^\/]*))?$/u);
if (match !== null) {
const [, idStr, trueText = "true", falseText = "false"] = match;
const id = Number.parseInt(idStr, 10);
return $gameSwitches.value(id) ? trueText : falseText;
} else {
throw new Error(`Invalid format: \\S[${params}]`);
}
};
PluginManager.registerCommand(PLUGIN_NAME, COMMAND_REPLACE, function () {
const PLUGIN_COMMAND_CODE = 357;
const PLUGIN_COMMAND_DETAIL_CODE = 657;
const rest = this._list.slice(this._index);
const result = rest.findIndex(command => {
const { code, parameters: p } = command;
switch (code) {
case PLUGIN_COMMAND_CODE: return p[0] !== PLUGIN_NAME || p[1] !== COMMAND_REPLACE;
case PLUGIN_COMMAND_DETAIL_CODE: return false;
default: true;
}
});
const end = result !== -1 ? result : rest.length;
const paths = rest.slice(0, end).flatMap(command => {
if (command.code === PLUGIN_COMMAND_CODE) {
const args = command.parameters[3];
const path = P.parse(args["path"], pathParser);
return [path];
} else {
return [];
}
});
this._index += end - 1;
if (this.nextEventCode() === PLUGIN_COMMAND_CODE) {
this._index++;
const command = this.currentCommand();
const [pluginName, commandName, , args] = command.parameters;
const newArgs = paths.reduce((args, path) => replace(args, path), args);
PluginManager.callCommand(this, pluginName, commandName, newArgs);
} else {
throw new Error("Arguments replacement target not found.");
}
});
Z.redef(PluginManager, base => ({
callCommand(self, pluginName, commandName, args) {
const paths = TARGETS.get(`${pluginName}:${commandName}`);
const newArgs = paths !== undefined ? replaceAll(args, paths) : args;
base(this).callCommand(self, pluginName, commandName, newArgs);
},
}));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment