nyacronomicon/js/plugins/KN_FloorManager.js
2026-03-18 00:45:28 -05:00

460 lines
20 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.

/*:
* @target MZ
* @plugindesc フロアマネージャー ※マップ名を参照してるので取扱注意。詳細はヘルプ
* @author kurogoma
* @base PluginCommonBase
* @help 名称一致で検索してるため、マップ名の変更には要注意(ハジメイセキなど) floorTable参照
*
* @command Initialize
* @desc 初期化
*
* @command CheckNextFloor
* @text 次の階層確認
* @desc 仮決定したマップ名を変数に格納(未訪問)
* @arg variableId @type variable @default 1
*
* @command MoveToNextFloor
* @text 次の階層移動
* @desc 仮決定済みマップへ移動(訪問済み)
*
* @command CheckNextRareFloor
* @text 次のレア階層確認
* @desc 仮決定したレアマップ名を変数に格納
* @arg variableId @type variable @default 1
*
* @command MoveToNextRareFloor
* @text 次のレア階層移動
* @desc 仮決定済みレアマップへ移動(階層は加算されない)
*
* @command StartTimer
* @text タイマー開始
* @desc ランタイマーを開始0からスタート
*
* @command StopTimer
* @text タイマー停止
* @desc ランタイマーを停止(クリア時に呼び出す)
*/
(() => {
'use strict';
const script = document.currentScript;
const param = PluginManagerEx.createParameter(script);
//
// FloorManager
//
class FloorManager {
constructor() {
this.currentFloor = 0; // 現在の階層
this.visitedFloorIds = [];
this.tempCheckFloorIds = {};
this.mapQueues = {}; // レベルごとのマップキュー(シャッフル済み)
this.rareMapQueue = []; // レアマップキュー(シャッフル済み)
this.runStartPlayTime = 0; // 開始時のPlayTime 0は未設定または開始直後とみなす
this.runEndPlayTime = 0; // 終了時のPlayTime
this.floorTable = [ // フロアテーブル
{ mapLevel: 1, min: 1, max: 1, type: "fixed", name: "Ruins of the Beginning" }, // 固定マップ難易度1
{ mapLevel: 1, min: 2, max: 3, type: "level", level: "Level 1" }, // 難易度1
{ mapLevel: 2, min: 4, max: 6, type: "level", level: "Level 2" }, // 難易度2
{ mapLevel: 3, min: 7, max: 9, type: "level", level: "Level 3" }, // 難易度3
{ mapLevel: 4, min: 10, max: 10, type: "fixed", name: "Hall of Giants" }, // ボス固定難易度4
{ mapLevel: 4, min: 11, max: 13, type: "level", level: "Level 4" }, // 難易度4
{ mapLevel: 5, min: 14, max: 16, type: "level", level: "Level 5" }, // 難易度5
{ mapLevel: 6, min: 17, max: 19, type: "level", level: "Level 6" }, // 難易度6
{ mapLevel: 7, min: 20, max: 20, type: "fixed", name: "Garden of Rot" }, // ボス固定難易度7
{ mapLevel: 7, min: 21, max: 23, type: "level", level: "Level 7" }, // 難易度7
{ mapLevel: 8, min: 24, max: 26, type: "level", level: "Level 8" }, // 難易度8
{ mapLevel: 9, min: 27, max: 29, type: "level", level: "Level 9" }, // 難易度9
{ mapLevel: 10, min: 30, max: 30, type: "fixed", name: "Altar of Dominion" }, // ボス固定難易度10
{ mapLevel: 10, min: 31, max: 31, type: "fixed", name: "Ruins of the End" }, // 固定マップ難易度10
{ mapLevel: 11, min: 32, max: 32, type: "fixed", name: "Demon of Greed" } // 難易度10
];
}
initialize() {
console.log("[FloorManager] initialize");
this.currentFloor = 0;
this.visitedFloorIds = [];
this.tempCheckFloorIds = {};
this.mapQueues = {};
this.rareMapQueue = [];
this.runStartPlayTime = 0;
this.runEndPlayTime = 0;
if (!$dataMapInfos) {
console.warn("[FloorManager] $dataMapInfos not loaded");
return;
}
this.loadAvailableMaps();
}
loadAvailableMaps() {
// --- 親「通常マップ」を検索 ---
const mainParentEntry = Object.entries($dataMapInfos)
.find(([id, info]) => info && info.name === "Normal Map");
if (!mainParentEntry) {
console.warn("[FloorManager] 親 'Normal Map' が見つかりません");
return;
}
const mainParentId = Number(mainParentEntry[0]);
// --- 「通常マップ」配下の子フォルダ(レベルや固定)を抽出 ---
const levelParents = Object.entries($dataMapInfos)
.filter(([id, info]) =>
info &&
info.parentId === mainParentId &&
(/^Level \d+$/.test(info.name) || /^ボスマップ\d+$/.test(info.name))
);
// --- レベルごとにマップを分類 ---
const parentIds = levelParents.map(([id]) => Number(id));
const allMaps = Object.entries($dataMapInfos)
.filter(([id, info]) => info && parentIds.includes(info.parentId))
.map(([id, info]) => ({
id: Number(id),
name: info.name,
order: info.order || 0,
parentId: info.parentId,
parentName: ($dataMapInfos[info.parentId] && $dataMapInfos[info.parentId].name) || ""
}));
// --- レベルごとにグループ化してシャッフル ---
this.mapQueues = {};
for (const map of allMaps) {
if (!this.mapQueues[map.parentName]) {
this.mapQueues[map.parentName] = [];
}
this.mapQueues[map.parentName].push(map);
}
// 各レベルのキューをソートorder順してからシャッフル
for (const level of Object.keys(this.mapQueues)) {
// まずorder順にソートツクールのツリー表示順
this.mapQueues[level].sort((a, b) => a.order - b.order);
// シャッフルONならランダム化
if (this.isShuffle()) {
this.shuffleArray(this.mapQueues[level]);
}
}
// --- レアマップ(親「レアマップ」)も取得 ---
const rareParentEntry = Object.entries($dataMapInfos)
.find(([id, info]) => info && info.name === "Rare Map");
if (rareParentEntry) {
const rareParentId = Number(rareParentEntry[0]);
this.rareMapQueue = Object.entries($dataMapInfos)
.filter(([id, info]) => info && info.parentId === rareParentId)
.map(([id, info]) => ({
id: Number(id),
name: info.name,
order: info.order || 0
}));
// まずorder順にソートツクールのツリー表示順
this.rareMapQueue.sort((a, b) => a.order - b.order);
// シャッフルONならランダム化
if (this.isShuffle()) {
this.shuffleArray(this.rareMapQueue);
}
} else {
console.warn("[FloorManager] 親 'Rare Map' が見つかりません");
}
// 初期化完了ログ(共通メソッドで出力)
this.logQueueState("初期化完了");
}
isShuffle() { return true; }
shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
/**
* キューの状態をログ出力(初期化・ロード時共通)
* @param {string} context - 呼び出し元の識別用文字列
*/
logQueueState(context) {
const sortedLevels = Object.keys(this.mapQueues || {}).sort((a, b) => {
const numA = parseInt(a.replace(/\D/g, '')) || 0;
const numB = parseInt(b.replace(/\D/g, '')) || 0;
return numA - numB;
});
console.log(`[FloorManager] ${context}`);
for (const level of sortedLevels) {
const queue = this.mapQueues[level];
console.log(` ${level}: [${queue.map(m => m.name).join(', ')}]`);
}
if (this.rareMapQueue && this.rareMapQueue.length > 0) {
console.log(` レアマップ: [${this.rareMapQueue.map(m => m.name).join(', ')}]`);
}
}
findFixedMapByName(bossName) {
const entry = Object.entries($dataMapInfos).find(
([, info]) => info && info.name === bossName
);
if (entry) {
return { id: Number(entry[0]), name: entry[1].name, order: entry[1].order || 0 };
}
return null;
}
getFloorInfo(floorNumber) {
const entry = this.floorTable.find(e => floorNumber >= e.min && floorNumber <= e.max);
return entry || { type: "level", level: "レベル1" };
}
checkFloor(eventId) {
if (this.tempCheckFloorIds[eventId] != null)
return this.tempCheckFloorIds[eventId];
const nextFloor = this.currentFloor + 1;
const info = this.getFloorInfo(nextFloor);
// --- 固定マップならここで確定 ---
if (info.type === "fixed") {
const map = this.findFixedMapByName(info.name);
if (map) {
this.tempCheckFloorIds[eventId] = map.id;
console.log(`[FloorManager] 固定階層 ${nextFloor} -> ${map.name}`);
return map.id;
}
}
// --- 通常レベルマップの抽選(キューから順番に取得) ---
const queue = this.mapQueues[info.level];
if (!queue || queue.length === 0) {
console.warn(`[FloorManager] ${info.level} のキューが空です`);
return null;
}
// キューの先頭から未使用のマップを探す
const usedFloors = Object.values(this.tempCheckFloorIds);
let chosenIndex = queue.findIndex(map =>
!this.visitedFloorIds.includes(map.id) &&
!usedFloors.includes(map.id)
);
// 全て使用済みの場合は先頭を使う(フォールバック)
if (chosenIndex === -1) {
console.warn(`[FloorManager] ${info.level} の未使用マップがありません。先頭を使用`);
chosenIndex = 0;
}
const chosen = queue[chosenIndex];
this.tempCheckFloorIds[eventId] = chosen.id;
console.log(`[FloorManager] ${info.level} -> ${chosen.name} (queue index: ${chosenIndex})`);
return chosen.id;
}
checkRareFloor(eventId) {
if (this.tempCheckFloorIds[eventId] != null)
return this.tempCheckFloorIds[eventId];
if (!this.rareMapQueue || this.rareMapQueue.length === 0) {
console.warn("[FloorManager] レアマップキューが空です");
return null;
}
// キューの先頭から未使用のマップを探す
const usedFloors = Object.values(this.tempCheckFloorIds);
let chosenIndex = this.rareMapQueue.findIndex(map =>
!this.visitedFloorIds.includes(map.id) &&
!usedFloors.includes(map.id)
);
// 全て使用済みの場合は先頭を使う(フォールバック)
if (chosenIndex === -1) {
console.warn("[FloorManager] 未使用レアマップがありません。先頭を使用");
chosenIndex = 0;
}
const chosen = this.rareMapQueue[chosenIndex];
this.tempCheckFloorIds[eventId] = chosen.id;
console.log(`[FloorManager] eventId:${eventId} -> rare map: ${chosen.name} (queue index: ${chosenIndex})`);
return chosen.id;
}
moveToNextFloor(eventId) {
const nextFloorId = this.tempCheckFloorIds[eventId];
if (!nextFloorId) return null;
this.currentFloor++;
this.visitedFloorIds.push(nextFloorId);
this.tempCheckFloorIds = {};
console.log(`[FloorManager] eventId:${eventId} -> floorId:${nextFloorId}(${$dataMapInfos[nextFloorId]?.name})`);
return nextFloorId;
}
moveToNextRareFloor(eventId) {
const nextFloorId = this.tempCheckFloorIds[eventId];
if (!nextFloorId) {
console.warn(`[FloorManager] eventId:${eventId} has no assigned rare floor.`);
return null;
}
// currentFloorは増やさない寄り道
this.visitedFloorIds.push(nextFloorId); // ← 重要:再抽選防止
this.tempCheckFloorIds = {};
console.log(`[FloorManager] eventId:${eventId} -> rareFloorId:${nextFloorId}(${$dataMapInfos[nextFloorId]?.name})`);
return nextFloorId;
}
getNextMapName(floorId) {
// 全キューから検索
for (const level of Object.keys(this.mapQueues)) {
const mapData = this.mapQueues[level].find(m => m.id === floorId);
if (mapData) return mapData.name;
}
// レアマップキューから検索
const rareData = this.rareMapQueue.find(m => m.id === floorId);
if (rareData) return rareData.name;
// フォールバック
if ($dataMapInfos[floorId]) return $dataMapInfos[floorId].name;
return "";
}
//=====================================================================
// タイマー機能($gameSystem.playtime() ベース)
//=====================================================================
// タイマー開始
startTimer() {
// 現在のプレイ時間(秒)を記録
this.runStartPlayTime = $gameSystem.playtime();
this.runEndPlayTime = 0; // 終了時間はクリア
console.log(`[FloorManager] タイマー開始 (Start: ${this.runStartPlayTime}s)`);
}
// タイマー停止(完了時刻を記録)
stopTimer() {
// 開始していなければ無視 (0の場合は開始直後と区別つかないが、実害少ないので許容)
this.runEndPlayTime = $gameSystem.playtime();
console.log(`[FloorManager] タイマー停止: ${this.getFormattedTime()}`);
}
// フォーマット済み時間文字列を取得H:MM:SS
getFormattedTime() {
const elapsedSeconds = Math.max(0, this.runEndPlayTime - this.runStartPlayTime);
const hours = Math.floor(elapsedSeconds / 3600);
const minutes = Math.floor((elapsedSeconds % 3600) / 60);
const seconds = elapsedSeconds % 60;
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
}
window.Kurogoma = window.Kurogoma || {};
window.Kurogoma.floorManager = new FloorManager();
const floorManager = window.Kurogoma.floorManager;
// --- セーブにfloorManagerの状態を含める ---
const _DataManager_makeSaveContents = DataManager.makeSaveContents;
DataManager.makeSaveContents = function() {
const contents = _DataManager_makeSaveContents.call(this);
if (floorManager) {
contents.floorManager = {
currentFloor: floorManager.currentFloor,
visitedFloorIds: floorManager.visitedFloorIds,
tempCheckFloorIds: floorManager.tempCheckFloorIds,
mapQueues: floorManager.mapQueues,
rareMapQueue: floorManager.rareMapQueue,
runStartPlayTime: floorManager.runStartPlayTime,
runEndPlayTime: floorManager.runEndPlayTime
};
}
return contents;
};
// --- ロード時に復元 ---
const _DataManager_extractSaveContents = DataManager.extractSaveContents;
DataManager.extractSaveContents = function(contents) {
_DataManager_extractSaveContents.call(this, contents);
if (floorManager && contents.floorManager) {
Object.assign(floorManager, contents.floorManager);
floorManager.logQueueState(`ロード完了 (階層: ${floorManager.currentFloor}, 訪問済み: ${floorManager.visitedFloorIds.length}, 経過: ${floorManager.getFormattedTime()})`);
} else {
console.warn("[FloorManager] セーブデータにfloorManager情報がない → 再初期化");
floorManager?.initialize();
}
};
//
// プラグインコマンド
//
PluginManagerEx.registerCommand(script, "Initialize", () => {
floorManager.initialize();
});
PluginManagerEx.registerCommand(script, "CheckNextFloor", function(args) {
const eventId = $gameMap._interpreter._eventId || 0;
const floorId = floorManager.checkFloor(eventId);
const mapName = floorManager.getNextMapName(floorId);
$gameVariables.setValue(args.variableId, mapName);
});
PluginManagerEx.registerCommand(script, "MoveToNextFloor", function() {
const eventId = $gameMap._interpreter._eventId || 0;
const floorId = floorManager.moveToNextFloor(eventId);
if (!floorId) return;
$gamePlayer.reserveTransferToEvent({ mapId: floorId, eventId: 2, direction: 0, fadeType: 0 });
this.setWaitMode("transfer");
});
PluginManagerEx.registerCommand(script, "CheckNextRareFloor", function(args) {
const eventId = $gameMap._interpreter._eventId || 0;
const floorId = floorManager.checkRareFloor(eventId);
const mapName = floorManager.getNextMapName(floorId);
$gameVariables.setValue(args.variableId, mapName);
});
PluginManagerEx.registerCommand(script, "MoveToNextRareFloor", function() {
const eventId = $gameMap._interpreter._eventId || 0;
const floorId = floorManager.moveToNextRareFloor(eventId);
if (!floorId) return;
$gamePlayer.reserveTransferToEvent({ mapId: floorId, eventId: 2, direction: 0, fadeType: 0 });
this.setWaitMode("transfer");
});
PluginManagerEx.registerCommand(script, "StartTimer", () => {
floorManager.startTimer();
});
PluginManagerEx.registerCommand(script, "StopTimer", () => {
floorManager.stopTimer();
});
//
// Game_Player 拡張
//
Game_Player.prototype.reserveTransferToEvent = function({ mapId, eventId, direction, fadeType }) {
this.reserveTransfer(mapId, 0, 0, direction, fadeType);
this._newEventId = eventId;
};
const _Game_Player_performTransfer = Game_Player.prototype.performTransfer;
Game_Player.prototype.performTransfer = function() {
if (this.isTransferring()) {
if (this._newMapId !== $gameMap.mapId() || this._needsMapReload) {
$gameMap.setup(this._newMapId);
this._needsMapReload = false;
}
if (this._newEventId > 0) {
const event = $gameMap.event(this._newEventId);
if (event) {
this._newX = event.x;
this._newY = event.y;
this._newDirection = event.event().pages[0].image.direction;
}
this._newEventId = 0;
}
this.setDirection(this._newDirection);
this.locate(this._newX, this._newY);
this.refresh();
this.clearTransferInfo();
}
};
})();