nyacronomicon/js/plugins/KN_StillManager.js
2026-03-12 22:39:10 -05:00

393 lines
15 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.

/*:ja
* @target MZ
* @author kurogoma knights
* @base PluginCommonBase
* @plugindesc スチルマネージャー (KN_Map依存/UniqueDataLoader参照)
*
* @help
* ■レイヤ(10~30)
* backX -> base -> face -> frontX
*
* ■ギャラリー
* ・指定シーンの登録スチル(解放済みのみ/全表示デバッグ)を左右キーで巡回
* ・キャンセルで終了
* ・HUDに index/total と解放数/登録数を表示
*
* @command SHOW_BY_ID
* @text 表示(ID)
* @desc 登録済みIDのスチルを表示します
* @arg id @type string @text ID
*
* @command HIDE
* @text 非表示
* @desc 表示中のスチルを全て消去します
*
* @command REGISTER_ALL
*
* @command OPEN_GALLERY
* @text ギャラリーを開く
* @desc 指定シーンのギャラリーを開始します(左右で切替/キャンセルで終了)
* @arg scene @type string @text シーンID
* @arg label @type string @text 表示シーン名
* @arg showAll @type boolean @default false @text すべて表示(デバッグ)
* @arg startIndex @type number @min 0 @default 0 @text 開始インデックス
*
*/
(() => {
'use strict';
const script = document.currentScript;
PluginManagerEx.createParameter(script);
// レイヤー数設定
const LAYER_COUNT = {
back: 3,
front: 9
};
// ピクチャID動的割当
// 10から順に back_5, back_4, back_3, back_2, back_1, base, face, front_1, front_2, front_3 ...
const PIC_ID = {};
let pid = Kurogoma.PIDConst.StillReservedStart;
for (let i = LAYER_COUNT.back; i >= 1; i--) PIC_ID[`back_${i}`] = pid++;
PIC_ID.base = pid++;
PIC_ID.face = pid++;
for (let i = 1; i <= LAYER_COUNT.front; i++) PIC_ID[`front_${i}`] = pid++;
// HUDウィンドウ
class Window_StillGalleryHUD extends Window_Base {
initialize(rect) {
super.initialize(rect);
this.opacity = 192;
this.contentsOpacity = 255;
this._title = '';
this._pageText = '';
}
setTexts(title, numerator, denominator) {
const pageText = `${numerator}/${denominator}`;
if (this._title !== title || this._pageText !== pageText) {
this._title = title;
this._pageText = pageText;
this.refresh();
}
}
refresh() {
this.contents.clear();
const cw = this.contentsWidth();
this.drawText(this._title, 12, 0, cw / 2, 'left');
this.drawText(this._pageText, cw / 2, 0, cw / 2 - 12, 'right');
}
}
// スチルマネージャ本体
class StillManager {
constructor() {
this.state = { scene: null };
for (const key of Object.keys(PIC_ID)) this.state[key] = "";
this._galleryActive = false;
this._galleryScene = null;
this._galleryLabel = null;
this._galleryShowAll = false;
this._galleryList = [];
this._galleryIndex = 0;
this._hud = null;
this._defs = {}; // スチル定義(セーブ対象外)
}
// スチル表示ID指定
showById(id, recordUnlock = true) {
this._ensureDefStore();
const rec = this._defs[id];
if (!rec) {
console.warn('[StillManager] 未登録のid:', id);
return false;
}
console.log("[StillManager] 表示中のid :", id);
const args = { scene: rec.scene };
for (const key of Object.keys(PIC_ID)) args[key] = rec[key];
this._applyShowArgs(args);
// 解放は記憶アイテム所持で判定するため、フラグ記録は不要
return true;
}
// 非表示
hide() {
Object.values(PIC_ID).forEach(id => $gameScreen.erasePicture(id));
for (const key of Object.keys(this.state)) {
if (key !== 'scene') this.state[key] = "";
}
}
// 定義登録
register(def) {
if (!def?.id) return console.warn('[StillManager] id がありません');
if (!def?.base) return console.warn('[StillManager] base は必須です:', def);
this._ensureDefStore();
const scene = def.scene || "";
if (!scene) return console.warn('[StillManager] sceneId を抽出できません:', def.base);
const rec = { id: String(def.id), scene };
for (const key of Object.keys(PIC_ID)) {
rec[key] = this._buildDefinitionPath(scene, def[key]);
}
this._defs[rec.id] = rec;
return true;
}
registerAll() {
const list = $dataUniques?.StillList;
if (!Array.isArray(list)) return console.warn("[StillManager] StillList.json が未ロード");
let count = 0;
this._ensureDefStore();
for (const def of list) if (this.register(def)) count++;
console.log(`[StillManager] ${count} 件 登録完了`);
}
/**
* 解放済みスチルID一覧を取得
* 記憶アイテム所持による解放StillMemoryMap経由
*/
getUnlockedIds(sceneId) {
this._ensureDefStore();
const unlockedSet = new Set();
// 1. 記憶アイテム所持による解放
const memoryMap = $dataUniques?.StillMemoryMap;
if (memoryMap && $gameParty) {
for (const [memoryItemName, stillIds] of Object.entries(memoryMap)) {
// 記憶アイテムを所持しているか
const item = $dataItems.find(i => i && i.name === memoryItemName);
if (item && $gameParty.hasItem(item)) {
// このアイテムに紐づくスチルIDを解放済みに追加
for (const stillId of stillIds) {
// このスチルが指定されたsceneIdに属するか確認
const def = this._defs[stillId];
if (def && def.scene === sceneId) {
unlockedSet.add(stillId);
}
}
}
}
}
return [...unlockedSet];
}
// ギャラリー
isGalleryActive() { return this._galleryActive; }
openGallery(sceneId, label = "", showAll = false, startIndex = 0) {
this._ensureDefStore();
let sceneIds = [];
if (sceneId.toLowerCase() === "all") {
sceneIds = [...new Set(Object.values(this._defs).map(r => r.scene))];
} else if (sceneId.includes(",")) {
sceneIds = sceneId.split(",").map(s => s.trim()).filter(Boolean);
} else {
sceneIds = [sceneId];
}
this._galleryScene = sceneId;
this._galleryLabel = label;
this._galleryShowAll = !!showAll;
const all = Object.values(this._defs)
.filter(r => sceneIds.includes(r.scene))
.map(r => r.id);
const unlocked = sceneIds.flatMap(s => this.getUnlockedIds(s));
this._galleryList = (showAll ? all : all.filter(id => unlocked.includes(id)));
if (!this._galleryList.length) {
$gameMessage.add(`ギャラリー: 表示対象がありませんscene=${sceneId}${showAll ? ", all" : ""}`);
return false;
}
this._galleryIndex = Math.max(0, Math.min(startIndex, this._galleryList.length - 1));
this._galleryActive = true;
this._ensureHud();
this._refreshHud();
this.showById(this._galleryList[this._galleryIndex], false);
$gameMap.hideUI?.();
$gameScreen.startTint([-68, -68, -68, 0], 1);
$gameMap.showGuide();
return true;
}
closeGallery() {
if (!this._galleryActive) return;
this._galleryActive = false;
this._galleryScene = null;
this._galleryLabel = null;
this._galleryList = [];
this._galleryIndex = 0;
this.hide();
this._disposeHud();
$gameMap.showUI?.();
$gameScreen.startTint([0, 0, 0, 0], 1);
$gameMap.hideGuide();
Input.clear();
}
nextEntry() {
if (!this._galleryActive) return;
this._galleryIndex = (this._galleryIndex + 1) % this._galleryList.length;
this.showById(this._galleryList[this._galleryIndex], false);
this._refreshHud();
}
prevEntry() {
if (!this._galleryActive) return;
this._galleryIndex = (this._galleryIndex - 1 + this._galleryList.length) % this._galleryList.length;
this.showById(this._galleryList[this._galleryIndex], false);
this._refreshHud();
}
updateGallery() {
if (!this._galleryActive) return;
if (Input.isTriggered('right') || Input.isRepeated('right') || Input.isTriggered('pagedown')) this.nextEntry();
else if (Input.isTriggered('left') || Input.isRepeated('left') || Input.isTriggered('pageup')) this.prevEntry();
else if (Input.isTriggered('cancel')) this.closeGallery();
}
_ensureHud() {
const scene = SceneManager._scene;
if (!(scene instanceof Scene_Map)) return;
if (this._hud && this._hud.parent) return;
const ww = 240;
const wh = Kurogoma.Layout.calcWindowHeight(1, false);
const rect = new Rectangle(4, 4, ww, wh);
this._hud = new Window_StillGalleryHUD(rect);
scene.addWindow(this._hud);
}
_disposeHud() {
if (!this._hud) return;
const p = this._hud.parent;
if (p) p.removeChild(this._hud);
this._hud.destroy?.();
this._hud = null;
}
_refreshHud() {
if (!this._hud) return;
const currentId = this._galleryList[this._galleryIndex];
let sceneIds = [];
if (this._galleryScene?.toLowerCase() === "all") {
sceneIds = [...new Set(Object.values(this._defs).map(r => r.scene))];
} else if (this._galleryScene?.includes(",")) {
sceneIds = this._galleryScene.split(",").map(s => s.trim()).filter(Boolean);
} else {
sceneIds = [this._galleryScene];
}
const allIds = Object.values(this._defs)
.filter(r => sceneIds.includes(r.scene))
.map(r => r.id);
const title = this._galleryLabel || this._galleryScene;
const currentPos = allIds.indexOf(currentId);
const numerator = currentPos >= 0 ? currentPos + 1 : this._galleryIndex + 1;
const denominator = allIds.length;
this._hud.setTexts(title, numerator, denominator);
}
_applyShowArgs(args) {
this.state.scene = args.scene;
for (const key of Object.keys(PIC_ID)) {
this._showLayerIfChanged(key, this._toDisplayValue(args[key]));
}
}
_showLayerIfChanged(layerKey, newPathNorm) {
const id = PIC_ID[layerKey];
const before = this.state[layerKey];
const next = (newPathNorm ?? before);
if (next === before) return;
if (next === "") {
$gameScreen.erasePicture(id);
this.state[layerKey] = "";
return;
}
const zoom = $gameScreen.zoomScale ? $gameScreen.zoomScale() : 1;
const scale = 100 / zoom;
$gameScreen.showPicture(id, next, 0, 0, 0, scale, scale, 255, 0);
this.state[layerKey] = next;
}
_toDisplayValue(v) {
if (!v) return "";
return this._stripImgPref(this._stripExt(v));
}
_ensureDefStore() {
// _defs はインスタンス変数(セーブ対象外)
}
_buildDefinitionPath(scene, v) {
return v ? `still/${scene}/${this._fname(v)}` : '';
}
_stripExt(s) { return String(s || '').replace(/\.[^/.]+$/, ''); }
_stripImgPref(s) { return String(s || '').replace(/^(?:img[\\/])?(?:pictures[\\/])?/i, ''); }
_fname(s) { return String(s || '').replace(/^.*[\\/]/, '').replace(/\.[^/.]+$/, ''); }
}
window.Kurogoma = window.Kurogoma || {};
window.Kurogoma.stillManager = new StillManager();
const stillManager = window.Kurogoma.stillManager;
// セーブデータロード後に定義を再登録
// _defs はセーブ対象外なのでロード後に毎回再構築が必要
const _DataManager_extractSaveContents = DataManager.extractSaveContents;
DataManager.extractSaveContents = function(contents) {
_DataManager_extractSaveContents.call(this, contents);
// 最新の定義を再登録
if ($dataUniques?.StillList) {
stillManager.registerAll();
}
};
// Map連動制御
const _Scene_Map_update = Scene_Map.prototype.update;
Scene_Map.prototype.update = function() {
if (stillManager?.isGalleryActive?.()) stillManager.updateGallery();
_Scene_Map_update.call(this);
};
const _Game_Player_canMove = Game_Player.prototype.canMove;
Game_Player.prototype.canMove = function() {
if (stillManager?.isGalleryActive?.()) return false;
return _Game_Player_canMove.call(this);
};
const _Scene_Map_processMapTouch = Scene_Map.prototype.processMapTouch;
Scene_Map.prototype.processMapTouch = function() {
if (stillManager?.isGalleryActive?.()) return;
_Scene_Map_processMapTouch.call(this);
};
const _Scene_Map_isMenuEnabled = Scene_Map.prototype.isMenuEnabled;
Scene_Map.prototype.isMenuEnabled = function() {
if (stillManager?.isGalleryActive?.()) return false;
return _Scene_Map_isMenuEnabled.call(this);
};
// プラグインコマンド
const expand = v => (typeof v === "string" ? v.replace(/\\V\[(\d+)\]/gi, (_, n) => $gameVariables.value(Number(n)) ?? "") : v);
PluginManagerEx.registerCommand(script, "SHOW_BY_ID", args => {
const id = expand(args.id);
$gameMap.hideUI?.();
stillManager.showById(id);
});
PluginManagerEx.registerCommand(script, "HIDE", () => { $gameMap.showUI?.(); stillManager.hide(); });
PluginManagerEx.registerCommand(script, "REGISTER_ALL", () => stillManager.registerAll());
PluginManagerEx.registerCommand(script, "OPEN_GALLERY", args => stillManager.openGallery(args.scene, args.label, args.showAll, args.startIndex));
})();