//============================================================================= // RPG Maker MZ - KN_MapBattleBattleSimulator.js //============================================================================= /*:ja * @target MZ * @author kurogoma knights * @plugindesc シンプルなマップバトルシミュレーター */ (() => { 'use strict'; const script = document.currentScript; const param = PluginManagerEx.createParameter(script); // 定数設定(すべて \c[16] で表示、先制は \c[6]、ボスは \c[2]、HP0は赤(\c[18])) const COLOR = "\\c[16]"; const RESET = "\\c[0]"; const RED = "\\c[18]"; const UP_COLOR = "\\c[24]"; // ステータス上昇時(緑) const DOWN_COLOR = "\\c[18]"; // ステータス低下時(赤) // 数値を右寄せ(指定桁数) function formatNum(num, width) { return String(num).padStart(width, " "); } // ラベルと数値の整形(ラベルと数値の間にスペースを入れる) function formatLabelValue(label, value, width) { return COLOR + label + RESET + " " + formatNum(value, width); } // ステータス増減に応じた色付き数値フォーマット(種族特攻用) function formatColoredValue(label, current, base, width) { let valueStr; if (current > base) { valueStr = UP_COLOR + formatNum(current, width) + RESET; } else if (current < base) { valueStr = DOWN_COLOR + formatNum(current, width) + RESET; } else { valueStr = formatNum(current, width); } return COLOR + label + RESET + " " + valueStr; } // 威力表示用ヘルパー:固定値ボーナスがある場合は (基礎値+補正値) 形式で表示 function formatPowerWithBonus(base, bonus) { if (bonus > 0) { return "(" + base + UP_COLOR + "+" + bonus + RESET + ")"; } else if (bonus < 0) { return "(" + base + DOWN_COLOR + bonus + RESET + ")"; } return String(base); } class MapBattleSimulator { // simulateBattle(): 実際のHPは変更せず、局所変数上でシミュレーションする。 // 返り値は各側の最終HP、受けたダメージ、ログ、勝者、及び先制側("actor" or "enemy")を含む。 static simulateBattle(actor, enemy) { const ctx = this._prepareBattleContext(actor, enemy); this._runSimulation(ctx); return this._buildResult(ctx); } // バトルコンテキスト準備(ステータス・ターン順・ダメージ計算) static _prepareBattleContext(actor, enemy) { // 種族特攻バフを適用したアクターのステータスを取得 const actorAtk = Kurogoma.RaceEffectManager.getModifiedActorAtk(enemy, actor.atk); const actorDef = Kurogoma.RaceEffectManager.getModifiedActorDef(enemy, actor.def); const actorAgi = Kurogoma.RaceEffectManager.getModifiedActorAgi(enemy, actor.agi); const actorStats = { name: actor.name(), startHp: actor.hp, hp: actor.hp, atk: actorAtk, def: actorDef, agi: actorAgi }; // 種族特攻を適用した敵のステータスを取得 const enemyAtk = Kurogoma.RaceEffectManager.getModifiedAtk(enemy, enemy.atk); const enemyDef = Kurogoma.RaceEffectManager.getModifiedDef(enemy, enemy.def); const enemyAgi = Kurogoma.RaceEffectManager.getModifiedAgi(enemy, enemy.agi); const enemyStats = { name: enemy.name(), startHp: enemy.hp, hp: enemy.hp, atk: enemyAtk, def: enemyDef, agi: enemyAgi }; // 先攻・攻撃回数を計算 const turnOrder = this._calcTurnOrder(actorStats.agi, enemyStats.agi); // ダメージ計算 const artifactMods = Kurogoma.ArtifactBonus.getModifiers(); const damage = this._calcDamage(actorStats, enemyStats, artifactMods); return { actor, enemy, actorStats, enemyStats, turnOrder, artifactMods, damage, actorHits: 0, enemyHits: 0 }; } // 先攻・攻撃回数計算 static _calcTurnOrder(actorAgi, enemyAgi) { const hasFirstStrike = Kurogoma.ArtifactBonus.hasFirstStrikeArtifact(); let actorAttackCount, enemyAttackCount, first; if (hasFirstStrike && actorAgi >= enemyAgi) { actorAttackCount = Math.min(1 + Math.floor((actorAgi - enemyAgi) / 5), 3); enemyAttackCount = 1; first = "actor"; } else if (hasFirstStrike && actorAgi < enemyAgi) { actorAttackCount = 1; enemyAttackCount = Math.min(1 + Math.floor((enemyAgi - actorAgi) / 5), 3); first = "actor"; } else if (actorAgi >= enemyAgi) { actorAttackCount = Math.min(1 + Math.floor((actorAgi - enemyAgi) / 5), 3); enemyAttackCount = 1; first = "actor"; } else { enemyAttackCount = Math.min(1 + Math.floor((enemyAgi - actorAgi) / 5), 3); actorAttackCount = 1; first = "enemy"; } return { actorAttackCount, enemyAttackCount, first }; } // ダメージ計算 static _calcDamage(actorStats, enemyStats, artifactMods) { const calcBaseDamage = (atk, def) => Math.max(atk - def, 1); const actorBaseDamage = calcBaseDamage(actorStats.atk, enemyStats.def); const enemyBaseDamage = calcBaseDamage(enemyStats.atk, actorStats.def); const actorDamage = Math.max(actorBaseDamage + artifactMods.attackBonus, 1 + artifactMods.attackBonus); const enemyDamage = Math.max(enemyBaseDamage - artifactMods.defenseBonus, 0); return { actorBaseDamage, enemyBaseDamage, actorDamage, enemyDamage }; } // 戦闘シミュレーション(ループ) static _runSimulation(ctx) { const { actorStats, enemyStats, turnOrder, damage } = ctx; const { actorAttackCount, enemyAttackCount, first } = turnOrder; const { actorDamage, enemyDamage } = damage; while (actorStats.hp > 0 && enemyStats.hp > 0) { if (first === "actor") { for (let i = 0; i < actorAttackCount; i++) { enemyStats.hp -= actorDamage; ctx.actorHits++; } if (enemyStats.hp <= 0) break; for (let i = 0; i < enemyAttackCount; i++) { actorStats.hp -= enemyDamage; ctx.enemyHits++; } } else { for (let i = 0; i < enemyAttackCount; i++) { actorStats.hp -= enemyDamage; ctx.enemyHits++; } if (actorStats.hp <= 0) break; for (let i = 0; i < actorAttackCount; i++) { enemyStats.hp -= actorDamage; ctx.actorHits++; } } } actorStats.hp = Math.max(actorStats.hp, 0); enemyStats.hp = Math.max(enemyStats.hp, 0); } // ステートアイコン取得 static _getStateIcons(battler, rotationIndex) { if (!battler || !battler.states) return ""; const stateList = battler.states().filter(state => state && state.iconIndex > 0); if (stateList.length === 0) return ""; const index = rotationIndex % stateList.length; const currentState = stateList[index]; return `\\i[${currentState.iconIndex}]`; } // 名前にアイコン追加 static _appendNameIcons(ctx) { const { actor, enemy, actorStats, enemyStats, turnOrder } = ctx; // ローテーションインデックスを初期化 if (actor._stateIconRotationIndex === undefined) { actor._stateIconRotationIndex = 0; } if (enemy._stateIconRotationIndex === undefined) { enemy._stateIconRotationIndex = 0; } // 固定アイコン(ボス、先制、敗北イベント)を名前に追加 if ($gameSystem.isBossBattle && $gameSystem.isBossBattle()) { enemyStats.name += "\\i[421]"; } if (enemy.enemy().meta.DefeatEvent) { enemyStats.name += "\\i[84]"; } if (turnOrder.first === "actor") { actorStats.name += "\\i[82]"; } else { enemyStats.name += "\\i[82]"; } // ステートアイコンを取得して追加 const actorStateIcons = this._getStateIcons(actor, actor._stateIconRotationIndex); const enemyStateIcons = this._getStateIcons(enemy, enemy._stateIconRotationIndex); if (actorStateIcons) actorStats.name += actorStateIcons; if (enemyStateIcons) enemyStats.name += enemyStateIcons; } // ログ行生成 static _buildLog(stats, baseStats, levelStr, hpLine, powerStr, attackCount, totalPower) { return "\\> " + stats.name + "\n" + "\\> " + levelStr + formatColoredValue("ATK", stats.atk, baseStats.atk, 2) + " " + formatColoredValue("DEF", stats.def, baseStats.def, 2) + " " + formatLabelValue("SPD", stats.agi, 2) + "\n" + hpLine + "\n" + "\\> " + COLOR + "Power" + RESET + " " + powerStr + "x" + attackCount + " " + COLOR + "Total" + RESET + " " + totalPower; } // 結果生成 static _buildResult(ctx) { const { actor, enemy, actorStats, enemyStats, turnOrder, artifactMods, damage, actorHits, enemyHits } = ctx; const { actorAttackCount, enemyAttackCount, first } = turnOrder; const { actorBaseDamage, enemyBaseDamage, actorDamage, enemyDamage } = damage; // 名前にアイコン追加 this._appendNameIcons(ctx); const enemyTotalPower = enemyDamage * enemyHits; const actorTotalPower = actorDamage * actorHits; // レベル表示(敵のみ) const enemyLevelStr = enemy.enemy().meta.EnemyLevel ? COLOR + "Lv." + RESET + String(enemy.enemy().meta.EnemyLevel).padStart(2, " ") + " " : ""; // HPログ行 const enemyFinalHPStr = enemyStats.hp === 0 ? RED + formatNum(enemyStats.hp, 3) + RESET : formatNum(enemyStats.hp, 3); const actorFinalHPStr = actorStats.hp === 0 ? RED + formatNum(actorStats.hp, 3) + RESET : formatNum(actorStats.hp, 3); const enemyHpLine = "\\> " + COLOR + "HP" + RESET + " " + formatNum(enemyStats.startHp, 3) + " → " + enemyFinalHPStr + (enemyStats.hp === 0 ? " \\i[1]" : ""); const actorHpLine = "\\> " + COLOR + "HP" + RESET + " " + formatNum(actorStats.startHp, 3) + " → " + actorFinalHPStr + (actorStats.hp === 0 ? " \\i[1]" : ""); // 威力表示 const actorPowerStr = formatPowerWithBonus(actorBaseDamage, artifactMods.attackBonus); const enemyPowerStr = formatPowerWithBonus(enemyBaseDamage, -artifactMods.defenseBonus); // ログ生成 const enemyLog = this._buildLog( enemyStats, enemy, enemyLevelStr, enemyHpLine, enemyPowerStr, enemyAttackCount, enemyTotalPower ); const actorLog = this._buildLog( actorStats, actor, "", actorHpLine, actorPowerStr, actorAttackCount, actorTotalPower ); return { enemyLog, actorLog, enemyFinalHp: enemyStats.hp, actorFinalHp: actorStats.hp, enemyDamageTaken: actorTotalPower, actorDamageTaken: enemyTotalPower, winner: (actorStats.hp > enemyStats.hp) ? "actor" : "enemy", first, actor, enemy }; } // runBattle():simulateBattle() の結果に基づき実際のHPにダメージを適用し、結果を返す static runBattle(actor, enemy) { const simResult = MapBattleSimulator.simulateBattle(actor, enemy); const actorDamageTaken = actor.hp - simResult.actorFinalHp; const enemyDamageTaken = enemy.hp - simResult.enemyFinalHp; actor.gainHp(-actorDamageTaken); enemy.gainHp(-enemyDamageTaken); return { enemyLog: simResult.enemyLog, actorLog: simResult.actorLog, enemyDamageTaken: enemyDamageTaken, actorDamageTaken: actorDamageTaken, winner: simResult.winner, first: simResult.first }; } } window.Kurogoma = window.Kurogoma || {}; window.Kurogoma.MapBattleSimulator = MapBattleSimulator; })();