1081 lines
27 KiB
JavaScript
1081 lines
27 KiB
JavaScript
/*:
|
|
* @target MZ
|
|
* @author F_
|
|
* @base Fs
|
|
* @base ApngCore
|
|
* @orderAfter Fs
|
|
*
|
|
* @plugindesc
|
|
*
|
|
* 独自のメニュー画面を実装します。
|
|
*
|
|
* @help
|
|
*
|
|
* 独自のメニュー画面を実装します。
|
|
*
|
|
*
|
|
* ■ アクター画像の設定
|
|
*
|
|
* 各アクターに対応する画像は img/pictures/ 以下に配置する必要があります。
|
|
*
|
|
* 配置した画像は「アクター画像設定」により各アクターと対応付けます。
|
|
*
|
|
*
|
|
*
|
|
* @param ActorImages
|
|
* @type struct<ActorImage>[]
|
|
* @default []
|
|
* @text アクター画像設定
|
|
* @desc アクターに対応する画像の設定。
|
|
*
|
|
* @param HelpWindowConfig
|
|
* @type struct<HelpWindowConfig>
|
|
* @text ヘルプウィンドウ設定
|
|
* @desc ヘルプウィンドウに関する設定。
|
|
*
|
|
* @param CommandWindowConfig
|
|
* @type struct<CommandWindowConfig>
|
|
* @text コマンドウィンドウ設定
|
|
* @desc コマンドウィンドウに関する設定。
|
|
*
|
|
* @param GoldWindowConfig
|
|
* @type struct<GoldWindowConfig>
|
|
* @text 所持金ウィンドウ設定
|
|
* @desc 所持金ウィンドウに関する設定。
|
|
*
|
|
* @param StatusWindowConfig
|
|
* @type struct<StatusWindowConfig>
|
|
* @text ステータスウィンドウ設定
|
|
* @desc ステータスウィンドウに関する設定。
|
|
*/
|
|
|
|
/*~struct~ActorImage:
|
|
* @param actorId
|
|
* @type actor
|
|
* @default 0
|
|
* @text 対象アクター
|
|
* @desc 画像を設定する対象のアクター。
|
|
*
|
|
* @param path
|
|
* @type file
|
|
* @dir img/pictures/
|
|
* @text 画像
|
|
* @desc アクターに対応する画像。
|
|
*/
|
|
|
|
/*~struct~HelpWindowConfig:
|
|
* @param lines
|
|
* @type number
|
|
* @min 1
|
|
* @max 9
|
|
* @default 1
|
|
* @text 行数
|
|
* @desc 説明文の表示行数。
|
|
*
|
|
* @param descriptions
|
|
* @type struct<CommandDescs>
|
|
* @text コマンド説明文
|
|
* @desc 各コマンドに対応する説明文。
|
|
*/
|
|
|
|
/*~struct~CommandDescs:
|
|
* @param item
|
|
* @type multiline_string
|
|
* @text アイテム
|
|
* @desc 「アイテム」コマンドの説明文。
|
|
*
|
|
* @param skill
|
|
* @type multiline_string
|
|
* @text スキル
|
|
* @desc 「スキル」コマンドの説明文。
|
|
*
|
|
* @param equip
|
|
* @type multiline_string
|
|
* @text 装備
|
|
* @desc 「装備」コマンドの説明文。
|
|
*
|
|
* @param status
|
|
* @type multiline_string
|
|
* @text ステータス
|
|
* @desc 「ステータス」コマンドの説明文。
|
|
*
|
|
* @param formation
|
|
* @type multiline_string
|
|
* @text 並び替え
|
|
* @desc 「並び替え」コマンドの説明文。
|
|
*
|
|
* @param options
|
|
* @type multiline_string
|
|
* @text オプション
|
|
* @desc 「オプション」コマンドの説明文。
|
|
*
|
|
* @param save
|
|
* @type multiline_string
|
|
* @text セーブ
|
|
* @desc 「セーブ」コマンドの説明文。
|
|
*
|
|
* @param load
|
|
* @type multiline_string
|
|
* @text ロード
|
|
* @desc 「ロード」コマンドの説明文。
|
|
*
|
|
* @param gameEnd
|
|
* @type multiline_string
|
|
* @text ゲーム終了
|
|
* @desc 「ゲーム終了」コマンドの説明文。
|
|
*/
|
|
|
|
/*~struct~CommandWindowConfig:
|
|
* @param useLoadCommand
|
|
* @type boolean
|
|
* @default true
|
|
* @text ロードコマンドの追加
|
|
* @desc 「ロード」コマンドを追加するかどうか。
|
|
*
|
|
* @param loadCommandName
|
|
* @parent useLoadCommand
|
|
* @type string
|
|
* @default ロード
|
|
* @text ロードコマンド名
|
|
* @desc 「ロード」コマンドの表示名。
|
|
*/
|
|
|
|
/*~struct~GoldWindowConfig:
|
|
* @param goldLabel
|
|
* @type string
|
|
* @default 所持金
|
|
* @text 所持金項目名
|
|
* @desc 所持金の左側に表示する項目名。
|
|
*
|
|
* @param playtimeLabel
|
|
* @type string
|
|
* @default プレイ時間
|
|
* @text プレイ時間項目名
|
|
* @desc プレイ時間の左側に表示する項目名。
|
|
*
|
|
* @param lineHeight
|
|
* @type number
|
|
* @default 36
|
|
* @text 行の高さ
|
|
* @desc 行の高さ(px)。
|
|
*
|
|
* @param fontSize
|
|
* @type number
|
|
* @default 26
|
|
* @text フォントサイズ
|
|
* @desc フォントサイズ(px)。
|
|
*/
|
|
|
|
/*~struct~StatusWindowConfig:
|
|
* @param columns
|
|
* @type number
|
|
* @min 1
|
|
* @max 8
|
|
* @default 4
|
|
* @text 列数
|
|
* @desc 同時に表示する列数。
|
|
*
|
|
* @param baseX
|
|
* @type number
|
|
* @default 0
|
|
* @text 基本水平位置
|
|
* @desc 基本情報のX方向の位置(px)。
|
|
*
|
|
* @param baseY
|
|
* @type number
|
|
* @default 0
|
|
* @text 基本垂直位置
|
|
* @desc 基本情報のY方向の位置(px)。
|
|
*
|
|
* @param imageY
|
|
* @type number
|
|
* @default 0
|
|
* @text アクター画像位置
|
|
* @desc アクター画像下端のY方向の位置(px)。
|
|
*
|
|
* @param gaugeY
|
|
* @type number
|
|
* @default 0
|
|
* @text ゲージ位置
|
|
* @desc ゲージ群上端のY方向の位置(px)。
|
|
*
|
|
* @param gaugeWidth
|
|
* @type number
|
|
* @default 128
|
|
* @text ゲージ長
|
|
* @desc ゲージのラベルを含めたX方向の長さ(px)。
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
{
|
|
const PLUGIN_NAME = "CustomMadeMenuScene";
|
|
|
|
const DEFAULT_COMMAND_DESCS = {
|
|
item: "",
|
|
skill: "",
|
|
equip: "",
|
|
status: "",
|
|
formation: "",
|
|
options: "",
|
|
save: "",
|
|
load: "",
|
|
gameEnd: "",
|
|
};
|
|
|
|
const DEFAULT_HELP_WINDOW_CONFIG = {
|
|
lines: 1,
|
|
descriptions: DEFAULT_COMMAND_DESCS,
|
|
};
|
|
|
|
const DEFAULT_COMMAND_WINDOW_CONFIG = {
|
|
useLoadCommand: true,
|
|
loadCommandName: "Load",
|
|
};
|
|
|
|
const DEFAULT_GOLD_WINDOW_CONFIG = {
|
|
goldLabel: "Gold",
|
|
playtimeLabel: "Play Time",
|
|
lineHeight: 36,
|
|
fontSize: 26,
|
|
};
|
|
|
|
const DEFAULT_STATUS_WINDOW_CONFIG = {
|
|
columns: 4,
|
|
baseX: 0,
|
|
baseY: 0,
|
|
imageY: 0,
|
|
gaugeY: 0,
|
|
gaugeWidth: 128,
|
|
};
|
|
|
|
const { P, Z } = Fs.v0_4;
|
|
|
|
const parseParam = P.begin(PluginManager.parameters(PLUGIN_NAME));
|
|
|
|
const ACTOR_IMAGE_MAP = parseParam(
|
|
"ActorImages",
|
|
P.validate(
|
|
P.like([
|
|
{
|
|
actorId: P.natural,
|
|
path: P.string,
|
|
},
|
|
]),
|
|
(value, ok, err) => {
|
|
const map = new Map();
|
|
for (const { actorId, path } of value) {
|
|
if (!map.has(actorId)) {
|
|
map.set(actorId, path);
|
|
} else {
|
|
return err(`duplicate actor ID: ${actorId}`);
|
|
}
|
|
}
|
|
return ok(map);
|
|
}
|
|
)
|
|
);
|
|
|
|
const HELP_WINDOW_CONFIG = parseParam(
|
|
"HelpWindowConfig",
|
|
P.optional(
|
|
P.like({
|
|
lines: P.natural,
|
|
descriptions: P.optional(
|
|
P.like({
|
|
item: P.string,
|
|
skill: P.string,
|
|
equip: P.string,
|
|
status: P.string,
|
|
formation: P.string,
|
|
options: P.string,
|
|
save: P.string,
|
|
load: P.string,
|
|
gameEnd: P.string,
|
|
}),
|
|
DEFAULT_COMMAND_DESCS
|
|
),
|
|
}),
|
|
DEFAULT_HELP_WINDOW_CONFIG
|
|
)
|
|
);
|
|
|
|
const COMMAND_WINDOW_CONFIG = parseParam(
|
|
"CommandWindowConfig",
|
|
P.optional(
|
|
P.like({
|
|
useLoadCommand: P.boolean,
|
|
loadCommandName: P.string,
|
|
}),
|
|
DEFAULT_COMMAND_WINDOW_CONFIG
|
|
)
|
|
);
|
|
|
|
const GOLD_WINDOW_CONFIG = parseParam(
|
|
"GoldWindowConfig",
|
|
P.optional(
|
|
P.like({
|
|
goldLabel: P.string,
|
|
playtimeLabel: P.string,
|
|
lineHeight: P.natural,
|
|
fontSize: P.natural,
|
|
}),
|
|
DEFAULT_GOLD_WINDOW_CONFIG
|
|
)
|
|
);
|
|
|
|
const STATUS_WINDOW_CONFIG = parseParam(
|
|
"StatusWindowConfig",
|
|
P.optional(
|
|
P.like({
|
|
columns: P.natural,
|
|
baseX: P.natural,
|
|
baseY: P.natural,
|
|
imageY: P.natural,
|
|
gaugeY: P.natural,
|
|
gaugeWidth: P.natural,
|
|
}),
|
|
DEFAULT_STATUS_WINDOW_CONFIG
|
|
)
|
|
);
|
|
|
|
const LOAD_COMMAND_SYMBOL = "load";
|
|
|
|
class ApngSprite extends PIXI.Sprite {
|
|
#apng;
|
|
#time;
|
|
#prevTime;
|
|
#canvas;
|
|
#prevCanvas;
|
|
#texture;
|
|
|
|
constructor() {
|
|
super(null);
|
|
|
|
this.#apng = undefined;
|
|
this.#time = 0;
|
|
this.#prevTime = -1;
|
|
this.#canvas = this.#initCanvas();
|
|
this.#prevCanvas = this.#initCanvas();
|
|
this.#texture = this.#initTexture(this.#canvas);
|
|
}
|
|
|
|
get time() {
|
|
return this.#time;
|
|
}
|
|
set time(value) {
|
|
this.#time = value;
|
|
}
|
|
|
|
setImage(apng) {
|
|
if (this.#apng !== apng) {
|
|
this.#apng = apng;
|
|
this.#prevTime = -1;
|
|
if (apng !== undefined) {
|
|
this.#canvas.width = apng.width;
|
|
this.#canvas.height = apng.height;
|
|
this.#prevCanvas.width = apng.width;
|
|
this.#prevCanvas.height = apng.height;
|
|
this.texture = this.#texture;
|
|
} else {
|
|
this.texture = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
update() {
|
|
this.#updateCanvas();
|
|
}
|
|
|
|
destroy(...args) {
|
|
const texture = this.#texture;
|
|
if (texture !== null) {
|
|
this.texture = this.#texture = null;
|
|
texture.destroy(true);
|
|
}
|
|
|
|
super.destroy(...args);
|
|
}
|
|
|
|
#initCanvas() {
|
|
return Object.assign(document.createElement("canvas"), {
|
|
width: 1,
|
|
height: 1,
|
|
});
|
|
}
|
|
|
|
#initTexture(canvas) {
|
|
return new PIXI.Texture(
|
|
new PIXI.BaseTexture(canvas, {
|
|
mipmap: PIXI.MIPMAP_MODES.OFF,
|
|
scaleMode: PIXI.SCALE_MODES.LINEAR,
|
|
})
|
|
);
|
|
}
|
|
|
|
#updateCanvas() {
|
|
const apng = this.#apng;
|
|
const time = this.#time;
|
|
const prevTime = this.#prevTime;
|
|
const canvas = this.#canvas;
|
|
const prevCanvas = this.#prevCanvas;
|
|
|
|
if (apng !== undefined) {
|
|
const data = this.#makeRenderData(apng, prevTime, time);
|
|
if (data !== undefined) {
|
|
this.#renderFrames(canvas, prevCanvas, data);
|
|
this.#texture.update();
|
|
}
|
|
this.#prevTime = time;
|
|
}
|
|
}
|
|
|
|
#makeRenderData(apng, prevTime, time) {
|
|
if (prevTime !== time) {
|
|
const prevOrder = this.#frameOrder(apng, prevTime);
|
|
const order = this.#frameOrder(apng, time);
|
|
if (prevTime < 0 || prevOrder !== order) {
|
|
return order !== -1
|
|
? this.#makeRangeRenderData(apng, prevOrder, order)
|
|
: this.#makeClearRenderData();
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
#frameOrder(apng, time) {
|
|
const { numPlays, duration, frames } = apng;
|
|
if (time >= 0) {
|
|
const maxLoops = numPlays !== 0 ? numPlays : Number.POSITIVE_INFINITY;
|
|
const loops =
|
|
duration !== 0 ? Math.min(Math.floor(time / duration), maxLoops) : 0;
|
|
const frame = this.#frameIndex(frames, time - loops * duration);
|
|
const order = frame !== -1 ? frame + loops * frames.length : -1;
|
|
return order;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#frameIndex(frames, position) {
|
|
let rest = position;
|
|
for (const [index, frame] of frames.entries()) {
|
|
if (rest <= frame.delay) {
|
|
return index;
|
|
}
|
|
rest -= frame.delay;
|
|
}
|
|
return frames.length - 1;
|
|
}
|
|
|
|
#makeRangeRenderData(apng, prevOrder, order) {
|
|
const { frames } = apng;
|
|
const { start, end } = this.#deltaFrameRange(frames, prevOrder, order);
|
|
const last = start - 1;
|
|
return {
|
|
lastFrame: last !== -1 ? frames[last] : undefined,
|
|
frames: this.#deltaFrames(frames, start, end),
|
|
};
|
|
}
|
|
|
|
#deltaFrameRange(frames, prevOrder, order) {
|
|
const prevIndex = prevOrder % frames.length;
|
|
const index = order % frames.length;
|
|
const start =
|
|
prevOrder !== -1 && prevOrder < order && prevIndex < index
|
|
? prevIndex + 1
|
|
: 0;
|
|
const end = index + 1;
|
|
return { start, end };
|
|
}
|
|
|
|
#deltaFrames(frames, start, end) {
|
|
const result = [];
|
|
const filledRects = [];
|
|
for (const [n, frame] of frames.slice(start, end).reverse().entries()) {
|
|
const { offsetX, offsetY, width, height, disposeOp } = frame;
|
|
if (n === 0 || disposeOp !== APNG.DISPOSE_OP_PREVIOUS) {
|
|
const minX = offsetX;
|
|
const maxX = offsetX + width;
|
|
const minY = offsetY;
|
|
const maxY = offsetY + height;
|
|
if (
|
|
filledRects.every(
|
|
(r) =>
|
|
minX < r.minX || maxX > r.maxX || minY < r.minY || maxY > r.maxY
|
|
)
|
|
) {
|
|
result.push(frame);
|
|
if (disposeOp === APNG.DISPOSE_OP_BACKGROUND) {
|
|
filledRects.push({ minX, maxX, minY, maxY });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result.reverse();
|
|
}
|
|
|
|
#makeClearRenderData() {
|
|
return { lastFrame: undefined, frames: [] };
|
|
}
|
|
|
|
#renderFrames(canvas, prevCanvas, data) {
|
|
const { lastFrame, frames } = data;
|
|
const context = canvas.getContext("2d");
|
|
const prevContext = prevCanvas.getContext("2d");
|
|
if (frames.length !== 0) {
|
|
let prevFrame = lastFrame;
|
|
for (const frame of frames) {
|
|
this.#renderSingleFrame(context, prevContext, frame, prevFrame);
|
|
prevFrame = frame;
|
|
}
|
|
} else {
|
|
this.#disposeOfPrevFrame(context, prevContext, lastFrame);
|
|
}
|
|
}
|
|
|
|
#renderSingleFrame(context, prevContext, frame, prevFrame) {
|
|
this.#disposeOfPrevFrame(context, prevContext, prevFrame);
|
|
this.#savePrevFrameIfRequired(context, prevContext, frame);
|
|
this.#drawFrameImage(context, frame);
|
|
}
|
|
|
|
#disposeOfPrevFrame(context, prevContext, prevFrame) {
|
|
if (prevFrame !== undefined) {
|
|
this.#applyDisposeOp(context, prevContext, prevFrame);
|
|
} else {
|
|
const { canvas } = context;
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
}
|
|
}
|
|
|
|
#applyDisposeOp(context, prevContext, frame) {
|
|
const { disposeOp } = frame;
|
|
switch (disposeOp) {
|
|
case APNG.DISPOSE_OP_NONE:
|
|
return this.#applyDisposeOpNone();
|
|
case APNG.DISPOSE_OP_BACKGROUND:
|
|
return this.#applyDisposeOpBackground(context, frame);
|
|
case APNG.DISPOSE_OP_PREVIOUS:
|
|
return this.#applyDisposeOpPrevious(context, prevContext, frame);
|
|
default:
|
|
throw new Error(`invalid APNG disposeOp: ${disposeOp}`);
|
|
}
|
|
}
|
|
|
|
#applyDisposeOpNone() {}
|
|
|
|
#applyDisposeOpBackground(context, frame) {
|
|
const { offsetX, offsetY, width, height } = frame;
|
|
context.clearRect(offsetX, offsetY, width, height);
|
|
}
|
|
|
|
#applyDisposeOpPrevious(context, prevContext, frame) {
|
|
const { offsetX, offsetY, width, height } = frame;
|
|
context.clearRect(offsetX, offsetY, width, height);
|
|
context.drawImage(
|
|
prevContext.canvas,
|
|
offsetX,
|
|
offsetY,
|
|
width,
|
|
height,
|
|
offsetX,
|
|
offsetY,
|
|
width,
|
|
height
|
|
);
|
|
}
|
|
|
|
#savePrevFrameIfRequired(context, prevContext, frame) {
|
|
const { offsetX, offsetY, width, height, disposeOp } = frame;
|
|
if (disposeOp === APNG.DISPOSE_OP_PREVIOUS) {
|
|
prevContext.clearRect(offsetX, offsetY, width, height);
|
|
prevContext.drawImage(
|
|
context.canvas,
|
|
offsetX,
|
|
offsetY,
|
|
width,
|
|
height,
|
|
offsetX,
|
|
offsetY,
|
|
width,
|
|
height
|
|
);
|
|
}
|
|
}
|
|
|
|
#drawFrameImage(context, frame) {
|
|
const { offsetX, offsetY, width, height, image } = frame;
|
|
if (this.#shouldClearRect(frame)) {
|
|
context.clearRect(offsetX, offsetY, width, height);
|
|
}
|
|
context.drawImage(image, offsetX, offsetY, width, height);
|
|
}
|
|
|
|
#shouldClearRect(frame) {
|
|
const { blendOp } = frame;
|
|
switch (blendOp) {
|
|
case APNG.BLEND_OP_SOURCE:
|
|
return true;
|
|
case APNG.BLEND_OP_OVER:
|
|
return false;
|
|
default:
|
|
throw new Error(`invalid APNG blendOp: ${blendOp}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
class MenuActorImageSprite extends Sprite {
|
|
initialize() {
|
|
super.initialize();
|
|
|
|
this._actor = null;
|
|
this._path = undefined;
|
|
this._time = 0;
|
|
this._innerSprite = this.initApngSprite();
|
|
}
|
|
|
|
initApngSprite() {
|
|
const sprite = new ApngSprite();
|
|
sprite.anchor.set(0.5, 1);
|
|
return this.addChild(sprite);
|
|
}
|
|
|
|
setup(actor) {
|
|
this._actor = actor;
|
|
this.updateBitmap();
|
|
}
|
|
|
|
update() {
|
|
super.update();
|
|
|
|
this.updateTime();
|
|
this.updateBitmap();
|
|
}
|
|
|
|
updateTime() {
|
|
this._time++;
|
|
}
|
|
|
|
updateBitmap() {
|
|
const ASSUMED_FPS = 60;
|
|
const actor = this._actor;
|
|
const time = this._time;
|
|
const sprite = this._innerSprite;
|
|
|
|
const path =
|
|
actor !== null ? ACTOR_IMAGE_MAP.get(actor.actorId()) : undefined;
|
|
if (this._path !== path) {
|
|
this._path = path;
|
|
ImageManager.loadApngPicture(path, (apng) => sprite.setImage(apng));
|
|
}
|
|
|
|
sprite.time = (time * 1000) / ASSUMED_FPS;
|
|
}
|
|
}
|
|
|
|
class MenuGaugeSprite extends Sprite_Gauge {
|
|
bitmapWidth() {
|
|
return STATUS_WINDOW_CONFIG.gaugeWidth;
|
|
}
|
|
}
|
|
|
|
class MenuHelpWindow extends Window_Help {
|
|
refresh() {
|
|
const text = this._text;
|
|
const lines = text.split("\n");
|
|
const rect = this.baseTextRect();
|
|
const lineHeight = this.lineHeight();
|
|
|
|
this.contents.clear();
|
|
for (const [i, line] of lines.entries()) {
|
|
this.drawText(
|
|
line,
|
|
rect.x,
|
|
rect.y + lineHeight * i,
|
|
rect.width,
|
|
"left"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class MenuGoldWindow extends Window_Gold {
|
|
initialize(...args) {
|
|
super.initialize(...args);
|
|
|
|
this._playtime = 0;
|
|
}
|
|
|
|
lineHeight() {
|
|
return GOLD_WINDOW_CONFIG.lineHeight;
|
|
}
|
|
|
|
update() {
|
|
super.update();
|
|
|
|
this.updatePlaytime();
|
|
}
|
|
|
|
updatePlaytime() {
|
|
const playtime = $gameSystem.playtime();
|
|
if (this._playtime !== playtime) {
|
|
this._playtime = playtime;
|
|
this.refreshPlaytimeValue();
|
|
}
|
|
}
|
|
|
|
resetFontSettings() {
|
|
super.resetFontSettings();
|
|
|
|
this.contents.fontSize = GOLD_WINDOW_CONFIG.fontSize;
|
|
}
|
|
|
|
refresh() {
|
|
this.contents.clear();
|
|
this.drawGoldLabel();
|
|
this.drawGoldValue();
|
|
this.drawPlaytimeLabel();
|
|
this.drawPlaytimeValue();
|
|
}
|
|
|
|
refreshPlaytimeValue() {
|
|
this.clearPlaytimeValue();
|
|
this.drawPlaytimeValue();
|
|
}
|
|
|
|
drawGoldLabel() {
|
|
const { x, y, width } = this.itemLineRect(0);
|
|
this.resetTextColor();
|
|
this.drawText(GOLD_WINDOW_CONFIG.goldLabel, x, y, width, "left");
|
|
}
|
|
|
|
drawGoldValue() {
|
|
const { x, y, width } = this.itemLineRect(1);
|
|
this.drawCurrencyValue(this.value(), this.currencyUnit(), x, y, width);
|
|
}
|
|
|
|
drawPlaytimeLabel() {
|
|
const { x, y, width } = this.itemLineRect(2);
|
|
this.resetTextColor();
|
|
this.drawText(GOLD_WINDOW_CONFIG.playtimeLabel, x, y, width, "left");
|
|
}
|
|
|
|
drawPlaytimeValue() {
|
|
const { x, y, width } = this.itemLineRect(3);
|
|
this.resetTextColor();
|
|
this.drawText($gameSystem.playtimeText(), x, y, width, "right");
|
|
}
|
|
|
|
clearPlaytimeValue() {
|
|
const { x, y, width, height } = this.itemLineRect(3);
|
|
this.contents.clearRect(x, y, width, height);
|
|
}
|
|
}
|
|
|
|
const swapSpriteGauge = Z.swapper("Sprite_Gauge");
|
|
const swapWindowSelectable = Z.swapper("Window_Selectable");
|
|
const swapWindowHelp = Z.swapper("Window_Help");
|
|
const swapWindowGold = Z.swapper("Window_Gold");
|
|
|
|
Z.redef(ImageManager, ($) => {
|
|
const PICTURE_DIR = "img/pictures/";
|
|
const APNG_EXT = ".png";
|
|
const ENCRYPTED_FILE_SUFFIX = "_";
|
|
|
|
$._apngEntries = new Map();
|
|
|
|
$.modifyApngEntry = function (url, modifyFn) {
|
|
const entries = this._apngEntries;
|
|
const oldEntry = entries.get(url);
|
|
const newEntry = modifyFn(oldEntry);
|
|
const nextEntry =
|
|
newEntry.status === "loaded"
|
|
? this.onApngEntryLoaded(newEntry)
|
|
: newEntry;
|
|
entries.set(url, nextEntry);
|
|
};
|
|
|
|
$.onApngEntryLoaded = function (entry) {
|
|
const { callbacks, value } = entry;
|
|
for (const callback of callbacks) {
|
|
callback(value);
|
|
}
|
|
return { ...entry, callbacks: [] };
|
|
};
|
|
|
|
$.loadApngPicture = function (filename, callback) {
|
|
this.loadApng(PICTURE_DIR, filename, callback);
|
|
};
|
|
|
|
$.loadApng = function (folder, filename, callback) {
|
|
if (filename !== "") {
|
|
const url = folder + Utils.encodeURI(filename) + APNG_EXT;
|
|
this.loadApngFromUrl(url, callback);
|
|
} else {
|
|
callback(APNG.EMPTY);
|
|
}
|
|
};
|
|
|
|
$.loadApngFromUrl = function (url, callback) {
|
|
this.modifyApngEntry(url, (entry) => {
|
|
if (entry !== undefined) {
|
|
return {
|
|
...entry,
|
|
callbacks: [...entry.callbacks, callback],
|
|
};
|
|
} else {
|
|
return this.startLoadApngEntry(
|
|
{
|
|
status: "none",
|
|
url,
|
|
callbacks: [callback],
|
|
},
|
|
(result) => this.applyApngLoadResult(url, result)
|
|
);
|
|
}
|
|
});
|
|
};
|
|
|
|
$.retryLoadApngFromUrl = function (url) {
|
|
this.modifyApngEntry(url, (entry) => {
|
|
if (entry.status === "error") {
|
|
return this.startLoadApngEntry(entry, (result) =>
|
|
this.applyApngLoadResult(url, result)
|
|
);
|
|
} else {
|
|
return entry;
|
|
}
|
|
});
|
|
};
|
|
|
|
$.applyApngLoadResult = function (url, result) {
|
|
this.modifyApngEntry(url, (entry) => {
|
|
const { url, callbacks } = entry;
|
|
if (result.ok) {
|
|
return {
|
|
status: "loaded",
|
|
url,
|
|
callbacks,
|
|
value: result.value,
|
|
};
|
|
} else {
|
|
return {
|
|
status: "error",
|
|
url,
|
|
callbacks,
|
|
error: result.error,
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|
|
$.startLoadApngEntry = function (entry, callback) {
|
|
const { url, callbacks } = entry;
|
|
const controller = new AbortController();
|
|
const { signal } = controller;
|
|
|
|
const invokeCallback = (result) => {
|
|
if (!signal.aborted) {
|
|
callback(result);
|
|
}
|
|
};
|
|
|
|
this.loadApngFromUrlAsync(url, { signal }).then(
|
|
(value) => invokeCallback({ ok: true, value }),
|
|
(error) => invokeCallback({ ok: false, error })
|
|
);
|
|
|
|
return {
|
|
status: "loading",
|
|
url,
|
|
callbacks,
|
|
controller,
|
|
};
|
|
};
|
|
|
|
$.loadApngFromUrlAsync = async function (url, options) {
|
|
const encrypted = Utils.hasEncryptedImages();
|
|
const actualUrl = encrypted ? url + ENCRYPTED_FILE_SUFFIX : url;
|
|
const response = await fetch(actualUrl, options);
|
|
const buffer = await response.arrayBuffer();
|
|
const data = encrypted ? Utils.decryptArrayBuffer(buffer) : buffer;
|
|
const apng = await APNG.parse(data);
|
|
return apng;
|
|
};
|
|
|
|
const base$clear = $.clear;
|
|
$.clear = function () {
|
|
base$clear.apply(this, arguments);
|
|
|
|
this.clearApngImages();
|
|
};
|
|
|
|
$.clearApngImages = function () {
|
|
const entries = this._apngEntries;
|
|
for (const entry of entries.values()) {
|
|
if (entry.status === "loading") {
|
|
entry.controller.abort();
|
|
}
|
|
}
|
|
entries.clear();
|
|
};
|
|
|
|
const base$isReady = $.isReady;
|
|
$.isReady = function () {
|
|
const normalReady = base$isReady.apply(this, arguments);
|
|
const apngReady = this.checkApngLoaded();
|
|
return normalReady && apngReady;
|
|
};
|
|
|
|
$.checkApngLoaded = function () {
|
|
const entries = Array.from(this._apngEntries.values());
|
|
const error = entries.find((entry) => entry.status === "error");
|
|
if (error !== undefined) {
|
|
const { url } = error;
|
|
const retry = this.retryLoadApngFromUrl.bind(this, url);
|
|
throw ["LoadError", url, retry];
|
|
} else {
|
|
return entries.every((entry) => entry.status === "loaded");
|
|
}
|
|
};
|
|
});
|
|
|
|
Z.redef(Window_MenuCommand.prototype, ($) => {
|
|
$.updateHelp = function () {
|
|
const { descriptions } = HELP_WINDOW_CONFIG;
|
|
const helpWindow = this._helpWindow;
|
|
if (helpWindow !== null) {
|
|
const symbol = this.currentSymbol();
|
|
const text =
|
|
symbol !== null && descriptions.hasOwnProperty(symbol)
|
|
? descriptions[symbol]
|
|
: "";
|
|
helpWindow.setText(text);
|
|
}
|
|
};
|
|
|
|
const base$addSaveCommand = $.addSaveCommand;
|
|
$.addSaveCommand = function () {
|
|
base$addSaveCommand.apply(this, arguments);
|
|
|
|
this.addLoadCommand();
|
|
};
|
|
|
|
$.addLoadCommand = function () {
|
|
const { useLoadCommand, loadCommandName } = COMMAND_WINDOW_CONFIG;
|
|
if (useLoadCommand) {
|
|
const enabled = this.isLoadEnabled();
|
|
this.addCommand(loadCommandName, LOAD_COMMAND_SYMBOL, enabled);
|
|
}
|
|
};
|
|
|
|
$.isLoadEnabled = function () {
|
|
return !DataManager.isEventTest() && DataManager.isAnySavefileExists();
|
|
};
|
|
});
|
|
|
|
Z.redef(Window_MenuStatus.prototype, ($) => {
|
|
$.maxCols = function () {
|
|
return STATUS_WINDOW_CONFIG.columns;
|
|
};
|
|
|
|
$.itemHeight = function () {
|
|
return this.innerHeight;
|
|
};
|
|
|
|
$.drawItemImage = function (_index) {};
|
|
|
|
$.drawItemStatus = function (index) {
|
|
const {
|
|
baseX: baseOffsetX,
|
|
baseY: baseOffsetY,
|
|
imageY: imageOffsetY,
|
|
gaugeY: gaugeOffsetY,
|
|
gaugeWidth,
|
|
} = STATUS_WINDOW_CONFIG;
|
|
|
|
const actor = this.actor(index);
|
|
const { x, y, width } = this.itemRect(index);
|
|
const lineHeight = this.lineHeight();
|
|
|
|
const baseX = x + baseOffsetX;
|
|
const baseY = y + baseOffsetY;
|
|
const imageX = x + width / 2;
|
|
const imageY = y + imageOffsetY;
|
|
const gaugeX = x + Math.floor((width - gaugeWidth) / 2);
|
|
const gaugeY = y + gaugeOffsetY;
|
|
|
|
this.drawActorName(actor, baseX, baseY + lineHeight * 0);
|
|
this.drawActorLevel(actor, baseX, baseY + lineHeight * 1);
|
|
this.placeActorImage(actor, imageX, imageY);
|
|
swapSpriteGauge(globalThis, MenuGaugeSprite, () => {
|
|
this.placeBasicGauges(actor, gaugeX, gaugeY);
|
|
});
|
|
};
|
|
|
|
$.placeActorImage = function (actor, x, y) {
|
|
const key = "actor%1-image".format(actor.actorId());
|
|
const sprite = this.createInnerSprite(key, MenuActorImageSprite);
|
|
sprite.setup(actor);
|
|
sprite.move(x, y);
|
|
sprite.show();
|
|
};
|
|
});
|
|
|
|
Z.redef(Scene_Menu.prototype, ($) => {
|
|
$.isBottomHelpMode = function () {
|
|
return false;
|
|
};
|
|
|
|
$.helpAreaHeight = function () {
|
|
return this.calcWindowHeight(HELP_WINDOW_CONFIG.lines, false);
|
|
};
|
|
|
|
const base$create = $.create;
|
|
$.create = function () {
|
|
base$create.apply(this, arguments);
|
|
|
|
this.createHelpWindow();
|
|
this._commandWindow.setHelpWindow(this._helpWindow);
|
|
};
|
|
|
|
const base$createHelpWindow = $.createHelpWindow;
|
|
$.createHelpWindow = function () {
|
|
swapWindowHelp(globalThis, MenuHelpWindow, () =>
|
|
base$createHelpWindow.apply(this, arguments)
|
|
);
|
|
};
|
|
|
|
const base$createCommandWindow = $.createCommandWindow;
|
|
$.createCommandWindow = function () {
|
|
base$createCommandWindow.apply(this, arguments);
|
|
|
|
this._commandWindow.setHandler(
|
|
LOAD_COMMAND_SYMBOL,
|
|
this.commandLoad.bind(this)
|
|
);
|
|
};
|
|
|
|
const base$createGoldWindow = $.createGoldWindow;
|
|
$.createGoldWindow = function () {
|
|
swapWindowGold(globalThis, MenuGoldWindow, () =>
|
|
base$createGoldWindow.apply(this, arguments)
|
|
);
|
|
};
|
|
|
|
$.goldWindowRect = function () {
|
|
return swapWindowSelectable(globalThis, MenuGoldWindow, () => {
|
|
const ww = this.mainCommandWidth();
|
|
const wh = this.calcWindowHeight(4, true);
|
|
const wx = this.isRightInputMode() ? Graphics.boxWidth - ww : 0;
|
|
const wy = this.mainAreaBottom() - wh;
|
|
return new Rectangle(wx, wy, ww, wh);
|
|
});
|
|
};
|
|
|
|
$.commandLoad = function () {
|
|
SceneManager.push(Scene_Load);
|
|
};
|
|
});
|
|
}
|