554 lines
No EOL
21 KiB
JavaScript
554 lines
No EOL
21 KiB
JavaScript
//=============================================================================
|
||
// RPG Maker MZ - KN_MapBattle.js
|
||
//=============================================================================
|
||
|
||
/*:ja
|
||
* @target MZ
|
||
* @author kurogoma knights
|
||
* @base PluginCommonBase
|
||
* @plugindesc マップバトル
|
||
*
|
||
* @command test
|
||
*
|
||
* @command SETUP_MAP_BATTLE
|
||
* @desc マップバトルの初期化 イベント名をキーにしてエネミーデータを検索する
|
||
*
|
||
* @command SHOW_ITEM_MENU
|
||
* @desc アイテムメニュー表示 スキルもスキルアイテムとして扱ってるので注意。
|
||
* @arg itemType @type select @option item @option skill @default item
|
||
* @desc 選択ウィンドウに表示するアイテムのタイプ
|
||
*
|
||
* @command USE_ITEM
|
||
* @desc 選択したアイテムを使用する。アイテム名と紐づいて処理が実行されるので注意。
|
||
*
|
||
* @command SIMULATE_BATTLE
|
||
* @arg resultSwitchId @text 勝利フラグを入れるスイッチ @type switch @default 0
|
||
*
|
||
* @command SHOW_SIMULATE_RESULT
|
||
* @arg variableId @text カーソル位置の変数 @type variable @default 0
|
||
* @arg victorySE @type file @dir audio/se/
|
||
* @arg defeatSE @type file @dir audio/se/
|
||
*
|
||
* @command RUN_BATTLE
|
||
*
|
||
* @command CHECK_BATTLE_END
|
||
*
|
||
* @command PROCESS_WIN_EVENT
|
||
*
|
||
* @command PROCESS_LOSE_EVENT
|
||
*
|
||
* @command ADD_STATE_TO_ENEMIES
|
||
* @desc 指定した名前の敵全てにステートを付与する
|
||
* @arg enemyName
|
||
* @text 敵の名前
|
||
* @type string
|
||
* @desc 対象となる敵の名前(この名前の敵全てに付与)
|
||
*
|
||
* @arg stateId
|
||
* @text ステートID
|
||
* @type state
|
||
* @desc 付与するステートのID
|
||
*
|
||
*/
|
||
|
||
|
||
(() => {
|
||
'use strict';
|
||
const script = document.currentScript;
|
||
const param = PluginManagerEx.createParameter(script);
|
||
|
||
//
|
||
// Window_BattleSimResult - シミュレーション結果表示用ウィンドウ
|
||
//
|
||
|
||
class Window_BattleSimResult extends Window_Base {
|
||
constructor(rect) {
|
||
super(rect);
|
||
this.openness = 0; // 初期状態で非表示
|
||
this._baseContent = "";
|
||
this._battler = null;
|
||
this._rotationTimer = 0;
|
||
}
|
||
|
||
setBattler(battler) {
|
||
this._battler = battler;
|
||
if (battler && !battler._stateIconRotationIndex) {
|
||
battler._stateIconRotationIndex = 0;
|
||
}
|
||
}
|
||
|
||
refresh(content) {
|
||
this._baseContent = content;
|
||
this.contents.clear();
|
||
this.drawTextEx(content, 0, 0);
|
||
this.open();
|
||
}
|
||
|
||
clear() {
|
||
this._baseContent = "";
|
||
this._battler = null;
|
||
this._rotationTimer = 0;
|
||
this.contents.clear();
|
||
this.close();
|
||
}
|
||
|
||
update() {
|
||
super.update();
|
||
if (this._battler && this._baseContent && this.isOpen() && this._battler.states) {
|
||
const stateList = this._battler.states().filter(state => state && state.iconIndex > 0);
|
||
if (stateList.length > 0) {
|
||
this._rotationTimer++;
|
||
if (this._rotationTimer >= 60) {
|
||
this._rotationTimer = 0;
|
||
this._battler._stateIconRotationIndex = (this._battler._stateIconRotationIndex + 1) % stateList.length;
|
||
this.refreshContent();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
refreshContent() {
|
||
if (this._baseContent && this._battler && this._battler.states) {
|
||
const stateList = this._battler.states().filter(state => state && state.iconIndex > 0);
|
||
if (stateList.length > 0) {
|
||
const currentState = stateList[this._battler._stateIconRotationIndex];
|
||
const newIconCode = `\\i[${currentState.iconIndex}]`;
|
||
const fixedIcons = [421, 84, 82, 1];
|
||
|
||
const updatedContent = this._baseContent.replace(
|
||
/\\i\[(\d+)\]/g,
|
||
(match, iconIndex) => {
|
||
const num = parseInt(iconIndex);
|
||
return fixedIcons.includes(num) ? match : newIconCode;
|
||
}
|
||
);
|
||
|
||
this.contents.clear();
|
||
this.drawTextEx(updatedContent, 0, 0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Scene_Map - 分割ウィンドウの追加
|
||
//
|
||
|
||
const _Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
|
||
Scene_Map.prototype.createAllWindows = function() {
|
||
_Scene_Map_createAllWindows.call(this);
|
||
|
||
const rect = this.messageWindowRect();
|
||
const wh = this.calcWindowHeight(4, false) + 8;
|
||
const rectLeft = new Rectangle(0, 0, rect.width / 2, wh);
|
||
const rectRight = new Rectangle(rect.width / 2, 0, rect.width / 2, wh);
|
||
|
||
this._leftSimWindow = new Window_BattleSimResult(rectLeft);
|
||
this.addWindow(this._leftSimWindow);
|
||
|
||
this._rightSimWindow = new Window_BattleSimResult(rectRight);
|
||
this.addWindow(this._rightSimWindow);
|
||
};
|
||
|
||
const _Window_Message_close = Window_Message.prototype.close;
|
||
Window_Message.prototype.close = function() {
|
||
_Window_Message_close.call(this);
|
||
const scene = SceneManager._scene;
|
||
if (scene instanceof Scene_Map) {
|
||
if (scene._leftSimWindow) scene._leftSimWindow.clear();
|
||
if (scene._rightSimWindow) scene._rightSimWindow.clear();
|
||
}
|
||
};
|
||
|
||
//
|
||
// Plugin Commands
|
||
//
|
||
|
||
// テストメソッド
|
||
PluginManagerEx.registerCommand(script, "test", args => {
|
||
$gameMap.knTest(args);
|
||
});
|
||
Game_Map.prototype.knTest = function() {
|
||
//
|
||
};
|
||
|
||
// マップバトル初期化!
|
||
PluginManagerEx.registerCommand(script, "SETUP_MAP_BATTLE", args => {
|
||
$gameMap.knMapBattleInit(args);
|
||
});
|
||
Game_Map.prototype.knMapBattleInit = function(args) {
|
||
const actor = $gameParty.leader(); // パーティのリーダー(Game_Actorインスタンス)
|
||
const interpreter = this.getInterpreter();
|
||
const eventId = interpreter.eventId();
|
||
const eventName = $dataMap.events[eventId]?.name;
|
||
// Look up via $dataTroops first: troop names (Japanese) match Map002 event names
|
||
// even when $dataEnemies names have been translated. Troop IDs align with enemy IDs.
|
||
const troop = $dataTroops.find(t => t && t.name === eventName);
|
||
const dataEnemy = troop ? $dataEnemies[troop.id] : $dataEnemies.find(e => e && e.name === eventName);
|
||
if (!dataEnemy) {
|
||
console.warn(`[KN_MapBattle] データが見つかりません: ${eventName} (eventId=${eventId})`);
|
||
return;
|
||
}
|
||
|
||
// Game_Actorを使ってプレイヤーデータを管理
|
||
this._knPlayer = actor;// 直接actor(Game_Actorインスタンス)を代入
|
||
|
||
// _knEnemies で敵ごとの状態を管理
|
||
if (!this._knEnemies) {
|
||
this._knEnemies = {}; // 敵ごとの状態を保持するオブジェクト
|
||
}
|
||
|
||
// すでに敵が初期化されていない場合のみ初期化
|
||
if (!this._knEnemies[eventId]) {
|
||
// Game_Enemyを使って敵データを管理
|
||
const knEnemy = new Game_Enemy(dataEnemy.id); // Game_Enemyインスタンスを生成
|
||
knEnemy.isBoss = Kurogoma.RaceEffect.isEnemyBoss(dataEnemy); // RaceEffectManager経由で判定
|
||
knEnemy._skillUsageCount = 0; // スキル使用回数
|
||
knEnemy._itemUsageCount = 0; // アイテム使用回数
|
||
knEnemy._skillUsageLimit = Number(dataEnemy.meta.SkillUsageLimit) || 1; // デフォルト1回
|
||
knEnemy._itemUsageLimit = Number(dataEnemy.meta.ItemUsageLimit) || 1; // デフォルト1回
|
||
|
||
// 敵の状態をイベントIDで管理
|
||
this._knEnemies[eventId] = knEnemy; // eventIdをキーにしてknEnemyを格納
|
||
}
|
||
|
||
// 現在の敵データを取得
|
||
this._knEnemy = this._knEnemies[eventId]; // 既に初期化されている敵データを取得
|
||
|
||
// 辞書登録
|
||
const enemyName = this._knEnemy.name();
|
||
const hiddenItem = $dataItems.find(item =>
|
||
item &&
|
||
item.name === enemyName &&
|
||
(item.itypeId === 3 || item.itypeId === 4) &&
|
||
!$gameParty.hasItem(item) // すでに持っている場合は除外
|
||
);
|
||
if (hiddenItem) {
|
||
$gameParty.gainItem(hiddenItem, 1); // アイテムを1つ入手
|
||
console.log(`"${hiddenItem.name}" を登録しました`);
|
||
}
|
||
};
|
||
|
||
|
||
// アイテム(スキル)選択画面を開く
|
||
PluginManagerEx.registerCommand(script, "SHOW_ITEM_MENU", args => {
|
||
$gameMap.knShowItemMenu(args.itemType);
|
||
});
|
||
Game_Map.prototype.knShowItemMenu = function(itemType) {
|
||
const args = { categoryType: itemType };
|
||
PluginManager.callCommand($gameMap.getInterpreter(), "KN_EventItem", "SET_CATEGORY", args);
|
||
$gameMessage.setItemChoice(1, 1); // $gameMessage.setItemChoice(代入する変数,アイテムタイプ[1:通常アイテム/2:大事なもの...])
|
||
this.getInterpreter().setWaitMode('message');
|
||
}
|
||
|
||
// アイテム(スキル)を使用する
|
||
PluginManagerEx.registerCommand(script, "USE_ITEM", args => {
|
||
$gameMap.knUseItem(args);
|
||
});
|
||
Game_Map.prototype.knUseItem = function(args) {
|
||
const item = $dataItems[$gameVariables.value(1)]; // 選択されたアイテム
|
||
if (!item)
|
||
return;
|
||
|
||
// 実行時に必要な情報をまとめて渡す
|
||
const context = {
|
||
subject: $gameParty.leader(),
|
||
target: this._knEnemy,
|
||
interpreter: this.getInterpreter(),
|
||
item: item,
|
||
};
|
||
Kurogoma.MapBattleActionManager.executeAction(item.name, context);
|
||
}
|
||
|
||
// シミュレートバトル
|
||
PluginManagerEx.registerCommand(script, "SIMULATE_BATTLE", args => {
|
||
$gameMap.knSimulateBattle(args);
|
||
});
|
||
Game_Map.prototype.knSimulateBattle = function(args) {
|
||
const actor = this._knPlayer;
|
||
const enemy = this._knEnemy;
|
||
if (!actor || !enemy) {
|
||
console.error('[KN_MapBattle] SIMULATE_BATTLE: _knPlayer or _knEnemy is undefined. SETUP_MAP_BATTLE may have failed.');
|
||
$gameSwitches.setValue(args.resultSwitchId, false);
|
||
return;
|
||
}
|
||
const result = Kurogoma.MapBattleSimulator.simulateBattle(actor, enemy);
|
||
this._lastSimResult = result;
|
||
$gameSwitches.setValue(args.resultSwitchId, result.winner == "actor");
|
||
}
|
||
|
||
// シミュレーション結果表示コマンド(先にシミュレートバトルを実行している想定)
|
||
PluginManagerEx.registerCommand(script, "SHOW_SIMULATE_RESULT", args => {
|
||
$gameMap.knShowSimResult(args);
|
||
});
|
||
Game_Map.prototype.knShowSimResult = function(args) {
|
||
const result = this._lastSimResult;
|
||
if (!result) {
|
||
console.warn('[KN_MapBattle] 直前のシミュレーション結果がありません');
|
||
return false;
|
||
}
|
||
|
||
// 変数設定(カーソル位置)
|
||
let selectIndex = 0;
|
||
if (Kurogoma.Option.isBattleSafeMode() || Kurogoma.Option.isBattleSkipMode()) {
|
||
selectIndex = result.winner == "actor" ? 0 : -1; // 敗北時は外す
|
||
}
|
||
$gameVariables.setValue(args.variableId, selectIndex);
|
||
const face = result.winner == "actor" ? "nyahuhun" : "bikkuri2";
|
||
$gameVariables.setValue(Kurogoma.VariableConst.Face, face); // 表情変数を指す
|
||
|
||
// SE再生
|
||
if (args.victorySE && result.winner == "actor") {
|
||
const victorySE = args.victorySE.split("/").pop();
|
||
playSe(victorySE);
|
||
} else if (args.defeatSE && result.winner != "actor") {
|
||
const defeatSE = args.defeatSE.split("/").pop();
|
||
playSe(defeatSE);
|
||
}
|
||
|
||
// 分割ウィンドウ表示
|
||
const scene = SceneManager._scene;
|
||
if (scene instanceof Scene_Map) {
|
||
const rect = scene.messageWindowRect();
|
||
const wh = scene.calcWindowHeight(4, false) + 8;
|
||
const positionType = 2; // 下
|
||
const y = (positionType * (Graphics.boxHeight - wh)) / 2 + rect.y;
|
||
|
||
scene._leftSimWindow.move(0, y, rect.width / 2, wh);
|
||
scene._rightSimWindow.move(rect.width / 2, y, rect.width / 2, wh);
|
||
|
||
scene._leftSimWindow.setBattler(result.enemy);
|
||
scene._rightSimWindow.setBattler(result.actor);
|
||
|
||
scene._leftSimWindow.refresh(result.enemyLog);
|
||
scene._rightSimWindow.refresh(result.actorLog);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// バトル実行
|
||
PluginManagerEx.registerCommand(script, "RUN_BATTLE", args => {
|
||
$gameMap.knRunBattle(args);
|
||
});
|
||
Game_Map.prototype.knRunBattle = function(args) {
|
||
const actor = this._knPlayer;
|
||
const enemy = this._knEnemy;
|
||
const context = {
|
||
subject: actor,
|
||
target: enemy,
|
||
item: { name: "通常攻撃", meta: { "倍率": 1, "TP消費": 0 } },
|
||
interpreter: $gameMap.getInterpreter(),
|
||
};
|
||
Kurogoma.MapBattleActionManager.executeAction("攻撃", context);
|
||
}
|
||
|
||
// バトル終了処理
|
||
PluginManagerEx.registerCommand(script, "CHECK_BATTLE_END", args => {
|
||
$gameMap.knCheckBattleEnd(args);
|
||
});
|
||
Game_Map.prototype.knCheckBattleEnd = function(args) {
|
||
this._knBattleResultType = "";
|
||
if (this._knEnemy.hp <= 0)
|
||
this._knBattleResultType = "win";
|
||
else if (this._knPlayer.hp <= 0)
|
||
this._knBattleResultType = "lose";
|
||
else
|
||
this._knBattleResultType = "draw";
|
||
}
|
||
|
||
// バトル終了処理(win)
|
||
PluginManagerEx.registerCommand(script, "PROCESS_WIN_EVENT", args => {
|
||
$gameMap.knActionWin(args);
|
||
});
|
||
Game_Map.prototype.knActionWin = function(args) {
|
||
const context = {
|
||
subject: this._knPlayer,
|
||
target: this._knEnemy,
|
||
interpreter: $gameMap.getInterpreter(),
|
||
};
|
||
Kurogoma.MapBattleActionManager.executeAction("勝利", context);
|
||
}
|
||
|
||
// バトル終了処理(lose)
|
||
PluginManagerEx.registerCommand(script, "PROCESS_LOSE_EVENT", args => {
|
||
$gameMap.knActionLose(args);
|
||
});
|
||
Game_Map.prototype.knActionLose = function(args) {
|
||
const context = {
|
||
subject: this._knPlayer,
|
||
target: this._knEnemy,
|
||
interpreter: $gameMap.getInterpreter(),
|
||
};
|
||
Kurogoma.MapBattleActionManager.executeAction("敗北", context);
|
||
}
|
||
|
||
// 指定した名前の敵全てにステートを付与
|
||
PluginManagerEx.registerCommand(script, "ADD_STATE_TO_ENEMIES", args => {
|
||
$gameMap.knAddStateToEnemies(args);
|
||
});
|
||
Game_Map.prototype.knAddStateToEnemies = function(args) {
|
||
const enemyName = args.enemyName;
|
||
const stateId = Number(args.stateId);
|
||
|
||
if (!enemyName || !stateId) {
|
||
console.warn('[KN_MapBattle] ADD_STATE_TO_ENEMIES: 敵の名前またはステートIDが指定されていません');
|
||
return;
|
||
}
|
||
|
||
// _knEnemiesの初期化
|
||
if (!this._knEnemies) {
|
||
this._knEnemies = {};
|
||
}
|
||
|
||
let count = 0;
|
||
// まず既に初期化されている敵をチェック
|
||
for (const eventId in this._knEnemies) {
|
||
const enemy = this._knEnemies[eventId];
|
||
if (enemy && enemy.name() === enemyName) {
|
||
count++;
|
||
}
|
||
}
|
||
|
||
// 見つからなければマップイベントから探して初期化
|
||
if (count === 0 && $dataMap && $dataMap.events) {
|
||
const dataEnemy = $dataEnemies.find(e => e && e.name === enemyName);
|
||
if (dataEnemy) {
|
||
for (let i = 1; i < $dataMap.events.length; i++) {
|
||
const event = $dataMap.events[i];
|
||
if (event && event.name === enemyName) {
|
||
// 敵を初期化
|
||
const knEnemy = new Game_Enemy(dataEnemy.id);
|
||
knEnemy.isBoss = Kurogoma.RaceEffect.isEnemyBoss(dataEnemy); // RaceEffectManager経由で判定
|
||
knEnemy._skillUsageCount = 0;
|
||
knEnemy._itemUsageCount = 0;
|
||
knEnemy._skillUsageLimit = Number(dataEnemy.meta.SkillUsageLimit) || 1;
|
||
knEnemy._itemUsageLimit = Number(dataEnemy.meta.ItemUsageLimit) || 1;
|
||
|
||
this._knEnemies[i] = knEnemy;
|
||
console.log(`[KN_MapBattle] "${enemyName}" (イベント${i}) を初期化しました`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ステート付与
|
||
count = 0;
|
||
for (const eventId in this._knEnemies) {
|
||
const enemy = this._knEnemies[eventId];
|
||
if (enemy && enemy.name() === enemyName) {
|
||
enemy.addState(stateId);
|
||
count++;
|
||
console.log(`[KN_MapBattle] "${enemyName}" (イベント${eventId}) にステート${stateId}を付与しました`);
|
||
}
|
||
}
|
||
|
||
if (count === 0) {
|
||
console.warn(`[KN_MapBattle] ADD_STATE_TO_ENEMIES: "${enemyName}" という名前の敵が見つかりませんでした`);
|
||
} else {
|
||
const stateName = $dataStates[stateId]?.name || `State ${stateId}`;
|
||
notify(`Applied ${stateName} to ${enemyName}...!`);
|
||
console.log(`[KN_MapBattle] ADD_STATE_TO_ENEMIES: ${count}体の敵にステートを付与しました`);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Game_Map
|
||
//
|
||
|
||
// バトル結果(主にイベントコマンドから参照する用)
|
||
Game_Map.prototype.isMapBattleResultWin = function(args) {
|
||
return this._knBattleResultType == "win";
|
||
}
|
||
Game_Map.prototype.isMapBattleResultLose = function(args) {
|
||
return this._knBattleResultType == "lose";
|
||
}
|
||
Game_Map.prototype.isMapBattleResultDraw = function(args) {
|
||
return this._knBattleResultType == "draw";
|
||
}
|
||
|
||
// 移動時に敵情報をクリアする
|
||
var _Game_Map_setup = Game_Map.prototype.setup;
|
||
Game_Map.prototype.setup = function(mapId) {
|
||
_Game_Map_setup.apply(this, arguments);
|
||
// _knEnemies をクリア(マップ移動時)
|
||
this._knEnemies = {};
|
||
}
|
||
|
||
// 現在の敵に対するスキル残り使用回数を取得(コマンドウィンドウ表示用)
|
||
Game_Map.prototype.getEnemyRemainingSkillCount = function() {
|
||
if (!this._knEnemy) return 0;
|
||
const actor = $gameParty.leader();
|
||
return Kurogoma.MapBattleActionManager.getRemainingUsageCount(this._knEnemy, actor, 'skill');
|
||
}
|
||
|
||
// 現在の敵に対するアイテム残り使用回数を取得(コマンドウィンドウ表示用)
|
||
Game_Map.prototype.getEnemyRemainingItemCount = function() {
|
||
if (!this._knEnemy) return 0;
|
||
const actor = $gameParty.leader();
|
||
return Kurogoma.MapBattleActionManager.getRemainingUsageCount(this._knEnemy, actor, 'item');
|
||
}
|
||
|
||
//
|
||
// Game_System
|
||
//
|
||
|
||
Game_System.prototype.isBossBattle = function() {
|
||
return $gameMap._knEnemy?.isBoss ?? false;
|
||
};
|
||
|
||
//
|
||
// Game_Actor
|
||
//
|
||
|
||
// レベルアップ時にメッセージ出す
|
||
const _Game_Actor_levelUp = Game_Actor.prototype.levelUp;
|
||
Game_Actor.prototype.levelUp = function() {
|
||
_Game_Actor_levelUp.call(this);
|
||
|
||
const se = { name: "Saint9", volume: 90, pitch: 100, pan: 0 };
|
||
AudioManager.playSe(se);
|
||
|
||
notify("Shiro leveled up!");
|
||
|
||
const args = {
|
||
character: "-1",
|
||
value: "Level UP!!",
|
||
setting: '{"reverse":"true", "type":"HP", "critical":"false"}',
|
||
};
|
||
PluginManager.callCommand($gameMap.getInterpreter(), "CharacterPopupDamage", "POPUP_DAMAGE", args);
|
||
|
||
// ボス撃破演出中はレベルアップフラッシュをスキップ
|
||
if (!$gameTemp._skipLevelUpFlash) {
|
||
$gameScreen.startFlash([255, 255, 255, 64], 20);
|
||
}
|
||
};
|
||
|
||
|
||
//
|
||
// Window_ChoiceList
|
||
//
|
||
|
||
// 連続でボタンを受け付けないようにした
|
||
Window_ChoiceList.prototype.isCancelTriggered = function() {
|
||
return Input.isTriggered('cancel');
|
||
};
|
||
|
||
|
||
//
|
||
// Utils
|
||
//
|
||
|
||
// メッセージぽい!
|
||
function notify(msg) {
|
||
Torigoya.NotifyMessage.Manager.notify(
|
||
new Torigoya.NotifyMessage.NotifyItem({ message: msg, icon: 0 })
|
||
);
|
||
}
|
||
|
||
// SE
|
||
function playSe(name) {
|
||
const se = { name: name, volume: 90, pitch: 100, pan: 0 };
|
||
AudioManager.playSe(se);
|
||
}
|
||
|
||
})(); |