nyacronomicon/js/plugins/KN_MapBattleSimulator.js

315 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//=============================================================================
// 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;
})();