//============================================================================= // Keke_LoopBalloon - ループ・フキダシアイコン // バージョン: 1.1.0 //============================================================================= // Copyright (c) 2022 ケケー // Released under the MIT license // http://opensource.org/licenses/mit-license.php //============================================================================= /*: * @target MZ * @plugindesc フキダシアイコンをループ表示させる * @author ケケー * @url https://kekeelabo.com * * @help * 【ver.1.1.0】 * ◎フキダシアイコンをループ表示できる * ◎プラグインコマンドや注釈でフキダシアイコンを制御できる * * * 【機能1】プラグインコマンドでフキダシアイコンを表示 * => プラグインコマンド → フキダシアイコンの表示 *  対象キャラ・フキダシアイコンIDを入力 * * * 【機能2】プラグインコマンドでフキダシアイコンを消去 * => プラグインコマンド → フキダシアイコンの消去 *  対象キャラを入力 * * * 【機能3】注釈でフキダシアイコンを表示 * => イベントコマンドの『注釈』を使う * ★注釈の置き場所は「現在のページの一番上」か「1ページ目の一番上」 *  まず現在のページのがあればそれを使い、なければ1ページ目のものを使う *  注釈は複数つなげることも可能 * 置いた注釈の中に * *  ※ループウェイトとループ回数は省略可能 * 例) * *  びっくりのフキダシアイコンを表示 * *  フキダシアイコンを表示しない * *  はてなのフキダシアイコンを表示 *  ループ間に 30フレーム のウェイトを挟み、 *  5回 表示したら終了 * * * 【付属】標準のアイコンID * 1: びっくり * 2: はてな * 3: 音符 * 4: ハート * 5: 怒り * 6: 汗 * 7: くしゃくしゃ * 8: 沈黙 * 9: 電球 * 10: Zzz * 11: ユーザー定義1 * 12: ユーザー定義2 * 13: ユーザー定義3 * 14: ユーザー定義4 * 15: ユーザー定義5 * * * ● 利用規約 ● * MITライセンスのもと、自由に使ってくれて大丈夫です * * * * @param 位置の自動調整 * @desc positionAdjust フキダシの位置をキャラの拡大率に応じて自動調整する * @type boolean * @default true * * @param サイズ自動調整 * @desc sizeAdjust フキダシの大きさをキャラの拡大率に応じて自動調整する * @type boolean * @default true * * * @command フキダシアイコンの表示 * @desc showBalloon フキダシアイコンをループ表示する * * @arg 対象キャラ-名前 * @desc taget-name ***:イベント検索、セルフ:イベント自身、プレイヤー:同、全:全キャラ、\v[***]:変数。, で複数指定 * * @arg 対象キャラ-ID * @desc taget-chara 1〜:イベントID。0:イベント自身、-1:プレイヤー、-2:全キャラ、\v[***]:変数, で複数、~ でまとめて指定 * * @arg フキダシアイコンID * @desc balloonId ループ表示するフキダシアイコンのID * @type number * * @arg ループウェイト * @desc roopWait ループの間に挟むウェイト。5 なら 5フレーム。0~5 なら 0 から 5 の間でランダム * * @arg ループ回数 * @desc roopNum ループ表示する回数。5 なら 5回 表示。0~5 なら 0 から 5 の間でランダム。空欄なら無制限 * * * * @command フキダシアイコンの消去 * @desc deleteBalloon 表示中のフキダシアイコンを消去する * * @arg 対象キャラ-名前 * @desc target-name ***:イベント検索、セルフ:イベント自身、プレイヤー:同、全:全キャラ、\v[***]:変数。, で複数指定 * * @arg 対象キャラ-ID * @desc target-id 1〜:イベントID。0:イベント自身、-1:プレイヤー、-2:全キャラ、\v[***]:変数, で複数、~ でまとめて指定 */ (() => { //- プラグイン名 const pluginName = document.currentScript.src.match(/^.*\/(.*).js$/)[1]; //================================================== //-- パラメータ受け取り //================================================== //- 真偽化 function toBoolean(str) { if (!str) { return false; } const str2 = str.toString().toLowerCase(); if (str2 == "true" || str2 == "on") { return true; } if (str2 == "false" || str2 == "off") { return false; } return Number(str); }; let parameters = PluginManager.parameters(pluginName); const keke_positionAdjust = toBoolean(parameters["位置の自動調整"]); const keke_sizeAdjust = toBoolean(parameters["サイズ自動調整"]); parameters = null; //================================================== //-- プラグインコマンド //================================================== //- フキダシアイコンの表示 PluginManager.registerCommand(pluginName, "フキダシアイコンの表示", args => { // フキダシIDを取得 const balloonId = Number(convertVariable(args["フキダシアイコンID"])); if (!balloonId) { return; } // 対象キャラを取得 const names = convertVariable(args["対象キャラ-名前"]); const ids = convertVariable(args["対象キャラ-ID"]); const charas = [...getCharasByName(names), ...getCharasById(ids)]; if (!charas || !charas.length) { return; } // 各パラメータを取得 const loopWait = convertVariable(args["ループウェイト"]); const loopNum = randomise(convertVariable(args["ループ回数"])); // ループフキダシをセット charas.forEach(chara => { // フキダシスプライトの消去 deleteBalloonSprite(chara); chara._loopBalloonKe = { id:balloonId, wait:loopWait, num:loopNum }; }) }); //- フキダシアイコンの消去 PluginManager.registerCommand(pluginName, "フキダシアイコンの消去", args => { // 対象キャラを取得 const names = convertVariable(args["対象キャラ-名前"]); const ids = convertVariable(args["対象キャラ-ID"]); const charas = [...getCharasByName(names), ...getCharasById(ids)]; if (!charas || !charas.length) { return; } // ループフキダシを消去 charas.forEach(chara => { // フキダシスプライトの消去 deleteBalloonSprite(chara); chara._loopBalloonKe = null; chara._loopBalloonCommentKe = null; // イベントのみフキダシ消去済みフラグをオン if (chara._eventId) { chara._loopBalloonDeletedKe = true; } }) }); //================================================== //-- ループフキダシ //================================================== //- ゲーム・キャラクターベース/更新(処理追加) const _Game_CharacterBase_update = Game_CharacterBase.prototype.update; Game_CharacterBase.prototype.update = function() { _Game_CharacterBase_update.apply(this); // ループフキダシの更新 updateLoopBalloon(this); }; //- ループフキダシの更新 function updateLoopBalloon(chara) { // 注釈からのフキダシ取得 getBalloonByComment(chara); const balloon = chara._loopBalloonKe || chara._loopBalloonCommentKe; // フキダシデータがなければリターン if (!balloon) { return; } // フキダシ表示中はリターン if (chara._balloonPlaying) { return; } // ウェイトを適用 if (balloon.waitDuration) { balloon.waitDuration--; return; } // フキダシを表示 $gameTemp.requestBalloon(chara, balloon.id); // ウェイトをセット balloon.waitDuration = randomise(balloon.wait); }; //- 注釈からのフキダシ取得 function getBalloonByComment(event) { // イベント以外はリターン if (!event._eventId) { return; } // 消去済みならリターン if (event._loopBalloonDeletedKe) { return; } // 前回のフキダシを保存 const preBalloon = event._loopBalloonCommentKe; // ページ切り替わったら初期化 initAtPageChange(event); // すでに取得済みならリターン if (event._loopBalloonCommentKe) { return; } // フキダシパラメータの取得 const p = getBalloonParameter(event); if (p) { event._loopBalloonCommentKe = p; } // フキダシが変更されたら if (preBalloon && (!p || preBalloon.id != p.id)) { // フキダシスプライトの消去 deleteBalloonSprite(event); } }; //- ページが切り替わったら初期化 function initAtPageChange(event) { // ページが切り替わったら if (event._currentPageKeLpbl != event._pageIndex) { // 注釈パラメータを初期化 event._loopBalloonCommentKe = null; } // 現在のページを保存 event._currentPageKeLpbl = event._pageIndex; }; //- フキダシパラメータの取得 function getBalloonParameter(event) { // 最初の注釈の取得 const comment = getFirstComment(event); if (!comment) { return null; } // 全ての注釈を処理 const regExp = /]+)>/i let p = null; // パラメータを検索 while (true) { // マッチング const match = comment.match(regExp); if (!match || !match[1]) { break; } // パラメータを取得 const descs = match[1].replace(/\s/g, "").split(","); p = {}; p.id = Number(descs[0]); p.wait = descs[1]; p.num = randomise(descs[2]); break; } return p; }; //- ゲーム・キャラクターベース/フキダシの終了(処理追加) const _Game_CharacterBase_endBalloon = Game_CharacterBase.prototype.endBalloon; Game_CharacterBase.prototype.endBalloon = function() { _Game_CharacterBase_endBalloon.apply(this); // フキダシの回数カウント countBalloonNum(this); }; //- フキダシの回数カウント function countBalloonNum(chara) { const balloon = chara._loopBalloonKe; if (!balloon || !balloon.num) { return; } balloon.num--; // 終了 if (balloon.num <= 0) { chara._loopBalloonKe = null; } }; //- フキダシスプライトの消去 function deleteBalloonSprite(chara) { const spriteset = SceneManager._scene._spriteset; const balloons = spriteset._balloonSprites; if (!balloons || !balloons.length) { return; } balloons.forEach(balloon => { if (balloon.targetObject == chara) { spriteset.removeBalloon(balloon); } }); }; //- スブライトフキダシ/破棄(処理追加) const _Sprite_Balloon_destroy = Sprite_Balloon.prototype.destroy; Sprite_Balloon.prototype.destroy = function(options) { // フキダシスプライト破棄時のエラー防止 if (!this._texture) { return; } _Sprite_Balloon_destroy.apply(this, arguments); }; //- ゲームインタープリター/クリア(処理追加) const _Game_Interpreter_clear = Game_Interpreter.prototype.clear; Game_Interpreter.prototype.clear = function() { _Game_Interpreter_clear.apply(this); // イベントのフキダシ消去済みフラグをオフ if ($gameMap) { $gameMap.events().forEach(event => event ? event._loopBalloonDeletedKe = null : null); } }; //================================================== //-- フキダシ位置調整 //================================================== //- スブライトフキダシ/位置の更新(処理追加) const _Sprite_Balloon_updatePosition = Sprite_Balloon.prototype.updatePosition; Sprite_Balloon.prototype.updatePosition = function() { _Sprite_Balloon_updatePosition.apply(this); // キャラスプライトを取得(顔グラメイクプラグインに対応) const target = this._target._faceBaseSpriteKeFcmk ? this._target._faceBaseSpriteKeFcmk._faceSprite : this._target; // 位置の自動調整 if (keke_positionAdjust) { this.y = this._target.y - target.height * target.scale.y + 8; } // サイズ自動調整 if (keke_sizeAdjust) { const scale = target.scale.y > 1 ? 1 + (target.scale.y - 1) / 2 : 1; this.scale.x = scale; this.scale.y = scale; } }; //================================================== //-- 文字列基本 /ベーシック //================================================== // 文字列の数字リスト化 function strToNumList(str) { const list = []; str = str.replace(/\[/g, ""); str = str.replace(/\]/g, ""); const strs = str.split(","); let s2 = null; for (let s of strs) { if (s.includes("~")) { s2 = s.split("~"); s2 = s2.map(s => Number(s)); if (s2[1] >= s2[0]) { for (let i = s2[0]; i <= s2[1]; i++) { list.push(i); } } else { for (let i = s2[1]; i <= s2[0]; i++) { list.push(i); } } } else { list.push(Number(s)); } }; return list; }; //- ランダマイズ function randomise(val) { if (typeof val != "string" || !val.includes("~")) { return val; } const vals = val.replace(/\s/g, "").split("~"); const calcs = [Number(vals[0]), Number(vals[1])]; calcs.sort((a, b) => a - b); return Math.round(calcs[0] + Math.random() * (calcs[1] - calcs[0])); }; //- 変数の置換 function convertVariable(str) { if (!str) { return str; } const regExp = /[\x1b\\]v\[(\d+)\]/gi; while (true) { const match = regExp.exec(str); if (!match) { break; } const val = $gameVariables.value(Number(match[1])); str = str.replace(match[0], val); } return str; }; //================================================== //-- イベント基本 /ベーシック //================================================== //- 最初の注釈の取得 function getFirstComment(event) { const comments = []; // 現在のページの最初の注釈を取得 const currentPage = event.page(); comments.push(getFirstComment_page(currentPage)); // 1ページの最初の注釈を取得 if (event._pageIndex != 0) { const onePage = event.event().pages[0]; comments.push(getFirstComment_page(onePage)); } // 注釈を全て統合 let comment = ""; comments.forEach(cmt => { if (!cmt) { return; } comment += cmt + "\n"; }); return comment; }; //- 最初の注釈の取得-ページ function getFirstComment_page(page) { if (!page) { return null; } let list = page.list; if (!list[0] || list[0].code != 108) { return null; } // 最初の注釈を全て読み込み let comment = ""; let i = 0; while (true) { const command = list[i]; if (!command || !(command.code == 108 || command.code == 408)) { break; } comment += command.parameters[0] + "\n"; i++; } return comment; }; //================================================== //-- プラグインコマンド基本 /ベーシック //================================================== let PlcmPreter = null; //- プラグインコマンド呼び出しプリターを保存(処理追加) const _PluginManager_callCommand = PluginManager.callCommand; PluginManager.callCommand = function(self, pluginName, commandName, args) { PlcmPreter = self; _PluginManager_callCommand.apply(this, arguments); PlcmPreter = null; }; //- プラグインコマンド呼び出しイベントを取得 function getPlcmEvent() { return getPreterEvent(PlcmPreter); }; //- インタープリターのイベント取得 function getPreterEvent(preter) { if (!preter) { return null; } return preter.character(preter.eventId()); }; //- 名前でのキャラリスト取得 function getCharasByName(names, self) { if (!names) { return []; } const nameList = names.replace(/\s/g, "").split(","); let charas = []; let match = null; for (const name of nameList) { // イベントを取得 $gameMap.events().forEach(event => { const note = event.event().name + " " + event.event().note; if (note.includes(name)) { charas.push(event); } }); // セルフを取得 if (name.match(/^(セルフ|自分|自身|self)$/)) { self = self || getPlcmEvent() || (charaSpritePlcm ? charaSpritePlcm._character : null) || $gamePlayer; if (self) { charas.push(self); } } // プレイヤーを取得 if (name.match(/^(プレイヤー|操作キャラ|player)$/)) { charas = [...charas, $gamePlayer]; } // フォロワーを取得 match = name.match(/^(フォロワー|フォロアー|隊列|隊列キャラ|follower)(\d*)$/) if (match) { const id = match[2] ? Number(match[2]) - 1 : 0; charas = id != null ? [...charas, $gamePlayer._followers._data[id]] : [...charas, ...$gamePlayer._followers._data]; } // パーティを取得 if (name.match(/^(パーティ|味方|味方全員|味方全体|party)$/)) { charas = [...charas, $gamePlayer, ...$gamePlayer._followers._data]; } // 乗り物を取得 match = name.match(/^(乗り物|乗物|乗機|vehicle)(\d*)$/); if (match) { const id = match[2] ? Number(match[2]) - 1 : 0; charas = id ? [...charas, $gameMap._vehicles[id]] : [...charas, ...$gameMap._vehicles]; } // 全て取得 if (name.match(/^(全て|すべて|全部|全体|all)$/)) { charas = [...$gameMap.events(), $gamePlayer, ...$gamePlayer._followers._data, ...$gameMap._vehicles]; } // 選択なし if (name.match(/^(なし|無し|none)$/)) { } } charas = charas.filter(chara => chara); return charas; }; //- IDでのキャラリスト取得 function getCharasById(ids, self) { if (!ids) { return []; } const idList = !ids ? [] : strToNumList(ids.toString()); let charas = []; for (const id of idList) { // イベントを取得 if (id > 0) { charas.push($gameMap.event(id)); } // セルフを取得 if (id == 0) { self = self || getPlcmEvent() || (charaSpritePlcm ?charaSpritePlcm._character : null) || $gamePlayer; if (self && !idList.includes(self._eventId)) { charas.push(self); } } // プレイヤーを取得 if (id == -1) { charas = [...charas, $gamePlayer]; } // フォロワーを取得 if (id <= -10 && id >= -99) { charas = id == -10 ? [...charas, ...$gamePlayer._followers._data] : [...charas, $gamePlayer._followers._data[Math.abs(id) - 11]]; } // 乗り物を取得 if (id <= -100) { charas = id == -100 ? [...charas, ...$gameMap._vehicles] : [...charas, $gameMap._vehicles[Math.abs(id) - 101]]; } // 全て取得 if (id == -2) { charas = [...$gameMap.events(), $gamePlayer, ...$gamePlayer._followers._data, ...$gameMap._vehicles]; } } charas = charas.filter(chara => chara); return charas; }; })();