/*:
* @target MZ
* @plugindesc Menu principal ultra stylisé - Inspiré Persona 5 + Street Fighter 6
* @author Akira for Akira1kurusu
* @version 1.7
*
* @param menuColor1
* @text Couleur principale
* @type string
* @default #E8003A
*
* @param menuColor2
* @text Couleur secondaire
* @type string
* @default #1A1A1A
*
* @param accentColor
* @text Couleur accent
* @type string
* @default #FFFFFF
*
* @param paintColor
* @text Couleur effet peinture
* @type string
* @default #FFD700
*
* @param transitionSpeed
* @text Vitesse de transition (frames)
* @type number
* @min 5
* @max 60
* @default 30
*
* @help
* UNIQUE MENU v1.7 — Persona 5 x Street Fighter 6
* Transitions stylisées, formation corrigée, gold window améliorée.
*/
(() => {
'use strict';
const pluginName = 'UniqueMenu_MZ';
const params = PluginManager.parameters(pluginName);
const COLOR1 = params.menuColor1 || '#E8003A';
const COLOR2 = params.menuColor2 || '#1A1A1A';
const ACCENT = params.accentColor || '#FFFFFF';
const PAINT = params.paintColor || '#FFD700';
const TRANS_SPEED = parseInt(params.transitionSpeed) || 30;
const GOLD_H = 96;
const BAR_WIDTH = 14;
// ============================================================
// UTILITAIRES CANVAS
// ============================================================
function getCtx(bmp) {
return bmp && bmp._canvas ? bmp._canvas.getContext('2d') : null;
}
function drawSkewedRect(ctx, x, y, w, h, skew, color, alpha) {
if (alpha === undefined) alpha = 1;
ctx.save();
ctx.globalAlpha = alpha; ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x + skew, y);
ctx.lineTo(x + w + skew, y);
ctx.lineTo(x + w - skew, y + h);
ctx.lineTo(x - skew, y + h);
ctx.closePath(); ctx.fill(); ctx.restore();
}
function drawPaintSplash(ctx, cx, cy, size, color, alpha) {
if (alpha === undefined) alpha = 0.7;
ctx.save();
ctx.globalAlpha = alpha; ctx.fillStyle = color;
const pts = 8 + Math.floor(Math.random() * 5);
ctx.beginPath();
for (let i = 0; i < pts * 2; i++) {
const ang = (i / (pts * 2)) * Math.PI * 2;
const r = (i % 2 === 0)
? size * (0.7 + Math.random() * 0.5)
: size * (0.3 + Math.random() * 0.3);
if (i === 0) ctx.moveTo(cx + Math.cos(ang) * r, cy + Math.sin(ang) * r);
else ctx.lineTo(cx + Math.cos(ang) * r, cy + Math.sin(ang) * r);
}
ctx.closePath(); ctx.fill(); ctx.restore();
}
function drawSpeedLines(ctx, cx, cy, count, length, color, alpha) {
if (alpha === undefined) alpha = 0.12;
ctx.save();
ctx.globalAlpha = alpha; ctx.strokeStyle = color; ctx.lineWidth = 1;
for (let i = 0; i < count; i++) {
const ang = (i / count) * Math.PI * 2;
const s = 20 + Math.random() * 30;
const e = s + length * (0.5 + Math.random() * 0.5);
ctx.beginPath();
ctx.moveTo(cx + Math.cos(ang) * s, cy + Math.sin(ang) * s);
ctx.lineTo(cx + Math.cos(ang) * e, cy + Math.sin(ang) * e);
ctx.stroke();
}
ctx.restore();
}
function drawBg(ctx, w, h, opts) {
opts = opts || {};
const bgAlpha = opts.bgAlpha !== undefined ? opts.bgAlpha : 0.93;
const barColor = opts.barColor || COLOR1;
const barSide = opts.barSide || 'left';
const barSize = opts.barSize || 6;
const splashes = Array.isArray(opts.splashes) ? opts.splashes : [];
const lines = opts.speedLines !== false;
ctx.save(); ctx.globalAlpha = bgAlpha; ctx.fillStyle = COLOR2;
ctx.fillRect(0, 0, w, h); ctx.restore();
ctx.save(); ctx.globalAlpha = 1; ctx.fillStyle = barColor;
if (barSide === 'left') ctx.fillRect(0, 0, barSize, h);
else ctx.fillRect(0, 0, w, barSize);
ctx.restore();
for (const s of splashes) drawPaintSplash(ctx, s.x, s.y, s.size, s.color, s.alpha);
if (lines) drawSpeedLines(ctx, w * 0.85, h * 0.5, 28, h * 0.6, PAINT, 0.05);
ctx.save(); ctx.strokeStyle = COLOR1; ctx.globalAlpha = 0.18;
ctx.lineWidth = 1.5; ctx.beginPath();
ctx.moveTo(w * 0.55, 0); ctx.lineTo(w, h * 0.4);
ctx.stroke(); ctx.restore();
}
function p5Text(ctx, text, x, y, opts) {
opts = opts || {};
const sz = opts.size || 16;
const color = opts.color || ACCENT;
ctx.save();
ctx.font = `bold ${sz}px "Arial Black", Arial, sans-serif`;
ctx.textBaseline = opts.baseline || 'middle';
ctx.textAlign = opts.align || 'left';
if (opts.shadow !== false) {
ctx.fillStyle = '#000'; ctx.globalAlpha = 0.65;
ctx.fillText(text, x + 2, y + 2);
}
ctx.fillStyle = color;
ctx.globalAlpha = opts.alpha !== undefined ? opts.alpha : 1;
ctx.fillText(text, x, y);
ctx.restore();
}
function drawStatBar(ctx, x, y, w, h, ratio, fillColor) {
ctx.save();
ctx.fillStyle = '#2a2a2a'; ctx.globalAlpha = 0.9;
ctx.fillRect(x, y, w, h);
ctx.fillStyle = fillColor; ctx.globalAlpha = 1;
ctx.fillRect(x, y, Math.max(0, w * ratio), h);
ctx.fillStyle = '#fff'; ctx.globalAlpha = 0.12;
ctx.fillRect(x, y, Math.max(0, w * ratio), Math.ceil(h / 2));
ctx.restore();
}
// ============================================================
// MIXIN STYLE GÉNÉRIQUE — sans toucher aux prototypes pour opacity
// ============================================================
function applyUniqueStyle(WinClass) {
const _init = WinClass.prototype.initialize;
WinClass.prototype.initialize = function (rect) {
_init.call(this, rect);
this.opacity = 0; this.frameVisible = false;
};
const _ref = WinClass.prototype.refresh;
WinClass.prototype.refresh = function () {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'left', barSize: 5, speedLines: false });
}
_ref.call(this);
};
}
[Window_ItemCategory, Window_SkillType, Window_EquipItem,
Window_SavefileList, Window_Options, Window_GameEnd
].forEach(applyUniqueStyle);
// ── WINDOW_EQUIPITEM drawItem — liste des items à équiper ──
Window_EquipItem.prototype.drawItem = function(index) {
const item = this.itemAt(index);
const rect = this.itemLineRect(index);
const ctx = getCtx(this.contents);
if (!ctx) return;
const isSelected = (index === this.index());
const enabled = item ? this.isEnabled(item) : false;
const pad = 4, skew = 8;
ctx.save();
ctx.fillStyle = '#000'; ctx.globalAlpha = 0.25;
ctx.fillRect(rect.x + pad, rect.y + pad, rect.width - pad * 2, rect.height - pad * 2);
ctx.restore();
drawSkewedRect(ctx, rect.x + pad, rect.y + pad,
rect.width - pad * 2, rect.height - pad * 2, skew,
isSelected ? COLOR1 : ACCENT, isSelected ? 0.30 : 0.07);
if (item) {
this.drawIcon(item.iconIndex, rect.x + pad + 4,
rect.y + (rect.height - ImageManager.iconHeight) / 2);
p5Text(ctx, item.name.toUpperCase(),
rect.x + pad + ImageManager.iconWidth + 10, rect.y + rect.height / 2,
{ size: 13, color: enabled ? (isSelected ? PAINT : ACCENT) : '#555', shadow: isSelected });
} else {
p5Text(ctx, '[ RETIRER ]', rect.x + rect.width / 2, rect.y + rect.height / 2,
{ size: 13, color: isSelected ? PAINT : '#666', shadow: false, align: 'center' });
}
// Séparateur
ctx.save();
ctx.strokeStyle = isSelected ? PAINT : COLOR1;
ctx.globalAlpha = isSelected ? 0.3 : 0.08;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(rect.x + pad + 4, rect.y + rect.height - 1);
ctx.lineTo(rect.x + rect.width - pad - 4, rect.y + rect.height - 1);
ctx.stroke();
ctx.restore();
};
Window_EquipItem.prototype.select = function(index) {
const prev = this._index !== undefined ? this._index : -1;
Window_ItemList.prototype.select.call(this, index);
if (prev !== index && this.contents) {
if (prev >= 0 && prev < this.maxItems()) this.redrawItem(prev);
if (index >= 0 && index < this.maxItems()) this.redrawItem(index);
}
};
// Window_Help
const _HI = Window_Help.prototype.initialize;
Window_Help.prototype.initialize = function (rect) {
_HI.call(this, rect); this.opacity = 0; this.frameVisible = false;
};
const _HR = Window_Help.prototype.refresh;
Window_Help.prototype.refresh = function () {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'top', barSize: 4, speedLines: false });
}
_HR.call(this);
};
// ============================================================
// WINDOW_ITEMLIST
// ============================================================
// ============================================================
// ============================================================
// WINDOW_ITEMCATEGORY — Style Persona 5 Clean (SAFE)
// ============================================================
// Style visuel des onglets
Window_ItemCategory.prototype.drawItem = function (index) {
const rect = this.itemRect(index);
const ctx = getCtx(this.contents);
if (!ctx) return;
const name = this.commandName(index);
const isSelected = (index === this.index());
const pad = 6;
const skew = 10;
// Fond oblique léger
drawSkewedRect(
ctx,
rect.x + pad,
rect.y + pad,
rect.width - pad * 2,
rect.height - pad * 2,
skew,
isSelected ? COLOR1 : ACCENT,
isSelected ? 0.25 : 0.08
);
// Trait sous l’onglet sélectionné
if (isSelected) {
ctx.save();
ctx.fillStyle = PAINT;
ctx.globalAlpha = 0.9;
ctx.fillRect(rect.x + pad + 8, rect.y + rect.height - 6,
rect.width - pad * 2 - 16, 3);
ctx.restore();
}
// Texte centré
p5Text(
ctx,
name.toUpperCase(),
rect.x + rect.width / 2,
rect.y + rect.height / 2,
{
size: 16,
color: isSelected ? ACCENT : "#888",
shadow: isSelected,
align: "center"
}
);
};
// Animation SAFE : on part de l'update d'origine
const _WIC_update = Window_ItemCategory.prototype.update;
Window_ItemCategory.prototype.update = function () {
_WIC_update.call(this); // <-- on garde tout le comportement normal
this._animFrame = (this._animFrame || 0) + 1;
// Redessine uniquement si l’index change
if (this._lastIndex !== this.index()) {
this._lastIndex = this.index();
this.refresh();
}
};
// ============================================================
// WINDOW_ITEMLIST — Style Persona 5 Clean (SAFE)
// ============================================================
const _ILI = Window_ItemList.prototype.initialize;
Window_ItemList.prototype.initialize = function (rect) {
_ILI.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
};
const _ILR = Window_ItemList.prototype.refresh;
Window_ItemList.prototype.refresh = function () {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) {
drawBg(ctx, this.contentsWidth(), this.contentsHeight(), {
barSide: 'left',
barSize: 5,
speedLines: false
});
}
}
_ILR.call(this);
};
Window_ItemList.prototype.drawItem = function (index) {
const item = this.itemAt(index);
if (!item) return;
const rect = this.itemLineRect(index);
const ctx = getCtx(this.contents);
if (!ctx) {
Window_Selectable.prototype.drawItem.call(this, index);
return;
}
const enabled = this.isEnabled(item);
const isSelected = (index === this.index());
const pad = 6;
const skew = 8;
// Fond oblique léger
drawSkewedRect(
ctx,
rect.x + pad,
rect.y + pad,
rect.width - pad * 2,
rect.height - pad * 2,
skew,
ACCENT,
isSelected ? 0.18 : 0.08
);
// Contour fin si sélectionné
if (isSelected) {
ctx.save();
ctx.strokeStyle = COLOR1;
ctx.globalAlpha = 0.9;
ctx.lineWidth = 2;
ctx.strokeRect(
rect.x + pad + 2,
rect.y + pad + 2,
rect.width - pad * 2 - 4,
rect.height - pad * 2 - 4
);
ctx.restore();
}
// Icône
this.drawIcon(
item.iconIndex,
rect.x + pad + 10,
rect.y + (rect.height - ImageManager.iconHeight) / 2
);
// Nom
p5Text(
ctx,
item.name.toUpperCase(),
rect.x + pad + 50,
rect.y + rect.height / 2,
{
size: 16,
color: enabled ? ACCENT : "#666",
shadow: true
}
);
// Quantité
const count = $gameParty.numItems(item);
const countStr = `×${count}`;
ctx.save();
ctx.font = 'bold 13px "Arial Black", Arial';
ctx.textBaseline = 'middle';
ctx.textAlign = 'right';
const badgeW = ctx.measureText(countStr).width + 18;
const badgeX = rect.x + rect.width - badgeW - pad - 6;
const badgeY = rect.y + pad + 6;
const badgeH = rect.height - pad * 2 - 12;
drawSkewedRect(
ctx,
badgeX,
badgeY,
badgeW,
badgeH,
4,
PAINT,
enabled ? 0.22 : 0.10
);
ctx.fillStyle = PAINT;
ctx.globalAlpha = enabled ? 1 : 0.4;
ctx.fillText(countStr, badgeX + badgeW - 8, rect.y + rect.height / 2);
ctx.restore();
};
// Filtrage des objets par catégorie (critique pour que la liste ne soit pas vide)
Window_ItemList.prototype.includes = function(item) {
switch (this._category) {
case 'item':
return DataManager.isItem(item) && item.itypeId === 1;
case 'weapon':
return DataManager.isWeapon(item);
case 'armor':
return DataManager.isArmor(item);
case 'keyItem':
return DataManager.isItem(item) && item.itypeId === 2;
default:
return false;
}
};
// ============================================================
// WINDOW_SKILLLIST
// ============================================================
const _SLI = Window_SkillList.prototype.initialize;
Window_SkillList.prototype.initialize = function (rect) {
_SLI.call(this, rect); this.opacity = 0; this.frameVisible = false;
};
const _SLR = Window_SkillList.prototype.refresh;
Window_SkillList.prototype.refresh = function () {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'left', barSize: 5, speedLines: false });
}
_SLR.call(this);
};
Window_SkillList.prototype.drawItem = function (index) {
const skill = this.itemAt(index);
if (!skill) return;
const rect = this.itemLineRect(index);
const ctx = getCtx(this.contents);
const actor = this._actor;
if (!ctx || !actor) { Window_Selectable.prototype.drawItem.call(this, index); return; }
const enabled = this.isEnabled(skill);
const pad = 4, skew = 8;
drawSkewedRect(ctx, rect.x + pad, rect.y + pad,
rect.width - pad * 2, rect.height - pad * 2, skew, ACCENT, enabled ? 0.07 : 0.03);
this.drawIcon(skill.iconIndex, rect.x + 8,
rect.y + (rect.height - ImageManager.iconHeight) / 2);
p5Text(ctx, skill.name.toUpperCase(), rect.x + 40, rect.y + rect.height / 2,
{ size: 15, color: enabled ? ACCENT : '#666', shadow: true });
const cost = actor.skillMpCost(skill);
const tp = actor.skillTpCost(skill);
if (cost > 0 || tp > 0) {
const label = cost > 0 ? `${cost} MP` : `${tp} TP`;
const lcolor = cost > 0 ? '#5599FF' : '#33DDAA';
ctx.save();
ctx.font = 'bold 12px "Arial Black", Arial, sans-serif';
ctx.textBaseline = 'middle'; ctx.textAlign = 'right';
const lw = ctx.measureText(label).width + 10;
drawSkewedRect(ctx, rect.x + rect.width - lw - pad - 4,
rect.y + pad + 2, lw, rect.height - pad * 2 - 4, 4, lcolor, 0.2);
ctx.fillStyle = lcolor; ctx.globalAlpha = enabled ? 1 : 0.4;
ctx.fillText(label, rect.x + rect.width - pad - 6, rect.y + rect.height / 2);
ctx.restore();
}
};
// ============================================================
// WINDOW_EQUIPSLOT
// ============================================================
const _ESI = Window_EquipSlot.prototype.initialize;
Window_EquipSlot.prototype.initialize = function (rect) {
_ESI.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
this.padding = 4;
};
// Hauteur des lignes de slot — bien aérées
Window_EquipSlot.prototype.itemHeight = function() {
const slots = this._actor ? this._actor.equipSlots().length : 5;
// Remplir EXACTEMENT la hauteur disponible — aucun scroll
return Math.floor(this.innerHeight / Math.max(1, slots));
};
Window_EquipSlot.prototype.maxScrollY = function() { return 0; };
const _ESR = Window_EquipSlot.prototype.refresh;
Window_EquipSlot.prototype.refresh = function () {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'left', barSize: 5, speedLines: false });
}
_ESR.call(this);
};
Window_EquipSlot.prototype.select = function(index) {
const prev = this._index !== undefined ? this._index : -1;
Window_Selectable.prototype.select.call(this, index);
if (prev !== index && this.contents) {
if (prev >= 0 && prev < this.maxItems()) this.redrawItem(prev);
if (index >= 0 && index < this.maxItems()) this.redrawItem(index);
}
};
Window_EquipSlot.prototype.drawItem = function (index) {
if (!this._actor) return;
const rect = this.itemLineRect(index);
const ctx = getCtx(this.contents);
if (!ctx) { Window_StatusBase.prototype.drawItem.call(this, index); return; }
const slotName = this.slotName(index);
const item = this._actor.equips()[index];
const isSelected = (index === this.index());
const pad = 4, skew = 10;
// Fond sombre de base
ctx.save();
ctx.fillStyle = '#000'; ctx.globalAlpha = 0.3;
ctx.fillRect(rect.x + pad, rect.y + pad, rect.width - pad * 2, rect.height - pad * 2);
ctx.restore();
// Fond oblique coloré si sélectionné
drawSkewedRect(ctx, rect.x + pad, rect.y + pad,
rect.width - pad * 2, rect.height - pad * 2, skew,
isSelected ? COLOR1 : ACCENT, isSelected ? 0.40 : 0.07);
// Liseré gauche rouge
ctx.save();
ctx.fillStyle = isSelected ? PAINT : COLOR1;
ctx.globalAlpha = isSelected ? 1 : 0.5;
ctx.fillRect(rect.x + pad, rect.y + pad + 2, 3, rect.height - pad * 2 - 4);
ctx.restore();
// Label slot (petit, à gauche)
p5Text(ctx, slotName.toUpperCase(), rect.x + pad + 10, rect.y + rect.height / 2 - 7,
{ size: 9, color: isSelected ? PAINT : COLOR1, shadow: false });
// Nom item (grand, centré-bas)
if (item) {
this.drawIcon(item.iconIndex,
rect.x + pad + 8, rect.y + rect.height / 2 + 2);
p5Text(ctx, item.name.toUpperCase(),
rect.x + pad + 8 + ImageManager.iconWidth + 6, rect.y + rect.height / 2 + 9,
{ size: 13, color: isSelected ? PAINT : ACCENT, shadow: true });
} else {
p5Text(ctx, '—', rect.x + pad + 10, rect.y + rect.height / 2 + 9,
{ size: 13, color: '#444', shadow: false });
}
// Séparateur bas
ctx.save();
ctx.strokeStyle = isSelected ? PAINT : COLOR1;
ctx.globalAlpha = isSelected ? 0.35 : 0.12;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(rect.x + pad + 4, rect.y + rect.height - 2);
ctx.lineTo(rect.x + rect.width - pad - 4, rect.y + rect.height - 2);
ctx.stroke();
ctx.restore();
};
// ============================================================
// WINDOW_STATUS
// ============================================================
const _WSI = Window_Status.prototype.initialize;
Window_Status.prototype.initialize = function (rect) {
_WSI.call(this, rect); this.opacity = 0; this.frameVisible = false;
};
Window_Status.prototype.refresh = function () {
if (!this.contents || !this._actor) return;
this.contents.clear();
const ctx = getCtx(this.contents);
const actor = this._actor;
const w = this.contentsWidth(), h = this.contentsHeight();
if (!ctx) { Window_StatusBase.prototype.refresh.call(this); return; }
drawBg(ctx, w, h, { barSide: 'left', barSize: 6, speedLines: true });
drawPaintSplash(ctx, w - 50, 30, 40, PAINT, 0.06);
drawPaintSplash(ctx, w - 30, h - 20, 25, COLOR1, 0.05);
// ── PORTRAIT ──────────────────────────────────────────
const faceW = 128, faceH = 128;
this.drawFace(actor.faceName(), actor.faceIndex(), 14, 14, faceW, faceH);
ctx.save();
ctx.strokeStyle = COLOR1; ctx.globalAlpha = 0.9; ctx.lineWidth = 3;
ctx.strokeRect(12, 12, faceW + 4, faceH + 4);
ctx.strokeStyle = PAINT; ctx.globalAlpha = 0.4; ctx.lineWidth = 1;
ctx.strokeRect(8, 8, faceW + 12, faceH + 12);
ctx.restore();
// ── NOM + CLASSE + NIVEAU ─────────────────────────────
const infoX = faceW + 28;
p5Text(ctx, actor.name().toUpperCase(), infoX, 28, { size: 28, color: ACCENT });
p5Text(ctx, actor.currentClass().name.toUpperCase(), infoX + 2, 60,
{ size: 14, color: COLOR1, shadow: false });
// Badge niveau
drawSkewedRect(ctx, infoX, 72, 100, 28, 7, COLOR1, 0.95);
p5Text(ctx, `LV. ${actor.level}`, infoX + 8, 86, { size: 16, color: '#000', shadow: false });
// ── HP / MP BARRES LARGES ─────────────────────────────
const hpRatio = actor.mhp > 0 ? actor.hp / actor.mhp : 0;
const mpRatio = actor.mmp > 0 ? actor.mp / actor.mmp : 0;
const hpColor = hpRatio > 0.5 ? '#00DD55' : hpRatio > 0.25 ? '#FFAA00' : COLOR1;
const mpColor = '#5599FF';
const barW = w - infoX - 16;
// HP
ctx.save();
ctx.font = 'bold 12px "Arial Black", Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = hpColor; ctx.globalAlpha = 1;
ctx.fillText('HP', infoX, 112);
ctx.font = '11px Arial'; ctx.fillStyle = ACCENT; ctx.globalAlpha = 0.95;
ctx.fillText(`${actor.hp} / ${actor.mhp}`, infoX + 28, 112);
ctx.restore();
drawStatBar(ctx, infoX, 122, barW, 9, hpRatio, hpColor);
// MP
ctx.save();
ctx.font = 'bold 12px "Arial Black", Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = mpColor; ctx.globalAlpha = 1;
ctx.fillText('MP', infoX, 142);
ctx.font = '11px Arial'; ctx.fillStyle = ACCENT; ctx.globalAlpha = 0.95;
ctx.fillText(`${actor.mp} / ${actor.mmp}`, infoX + 28, 142);
ctx.restore();
drawStatBar(ctx, infoX, 152, barW, 9, mpRatio, mpColor);
// EXP
const expRate = actor.isMaxLevel() ? 1
: (actor.currentExp() - actor.currentLevelExp()) /
(actor.nextLevelExp() - actor.currentLevelExp());
ctx.save();
ctx.font = 'bold 10px "Arial Black", Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = PAINT; ctx.globalAlpha = 1;
ctx.fillText('EXP', infoX, 170);
ctx.font = '10px Arial'; ctx.fillStyle = ACCENT; ctx.globalAlpha = 0.7;
const expPct = Math.floor(expRate * 100);
ctx.fillText(`${expPct}%`, infoX + 32, 170);
ctx.restore();
drawStatBar(ctx, infoX, 178, barW, 6, expRate, PAINT);
// ── SÉPARATEUR ────────────────────────────────────────
ctx.save(); ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.55;
ctx.fillRect(10, faceH + 22, w - 20, 2); ctx.restore();
// ── GRILLE DE STATS ──────────────────────────────────
// 4 colonnes x 2 lignes pour plus de lisibilité
const stats = [
{ label: 'ATK', val: actor.atk, color: '#FF6666' },
{ label: 'DEF', val: actor.def, color: '#66AAFF' },
{ label: 'M.ATK', val: actor.mat, color: '#CC66FF' },
{ label: 'M.DEF', val: actor.mdf, color: '#66CCFF' },
{ label: 'VITESSE', val: actor.agi, color: '#66FF99' },
{ label: 'CHANCE', val: actor.luk, color: PAINT },
{ label: 'HP MAX', val: actor.mhp, color: hpColor },
{ label: 'MP MAX', val: actor.mmp, color: mpColor },
];
const gridY = faceH + 30;
const cols = 4;
const colW = Math.floor((w - 20) / cols);
const rowH = Math.floor((h - gridY - 8) / 2);
stats.forEach((st, i) => {
const col = i % cols;
const row = Math.floor(i / cols);
const sx = 10 + col * colW;
const sy = gridY + row * rowH;
// Fond de la cellule
drawSkewedRect(ctx, sx + 2, sy + 3, colW - 4, rowH - 6, 4, ACCENT, 0.06);
// Trait coloré à gauche de la cellule
ctx.save();
ctx.fillStyle = st.color; ctx.globalAlpha = 0.85;
ctx.fillRect(sx + 2, sy + 5, 3, rowH - 10);
ctx.restore();
// Label
p5Text(ctx, st.label, sx + 10, sy + rowH / 2 - 8,
{ size: 9, color: st.color, shadow: false });
// Valeur (grande)
p5Text(ctx, String(st.val), sx + 10, sy + rowH / 2 + 8,
{ size: 18, color: ACCENT, shadow: true });
});
};
// ============================================================
// WINDOW_MENU_COMMAND
// ============================================================
class Window_UniqueMenuCommand extends Window_MenuCommand {
constructor(rect) {
super(rect);
this._splashes = [];
this._hoverAlphas = new Array(10).fill(0);
this._animFrame = 0;
this._lastIndex = -1;
this._selectedFlash = 0;
this.opacity = 0;
this.frameVisible = false;
this._generateItemSplashes();
}
_generateItemSplashes() {
this._splashes = [];
const w = this.width || 300, h = this.height || 400;
for (let i = 0; i < 8; i++) {
this._splashes.push({
x: w * 0.75 + Math.random() * w * 0.5,
y: 20 + Math.random() * h,
size: 12 + Math.random() * 30,
color: Math.random() > 0.6 ? COLOR1 : PAINT,
alpha: 0.05 + Math.random() * 0.09
});
}
}
itemHeight() { return 62; }
itemPadding() { return 16; }
maxCols() { return 1; }
update() {
super.update();
this._animFrame++;
if (!Array.isArray(this._hoverAlphas)) this._hoverAlphas = new Array(10).fill(0);
if (!Array.isArray(this._splashes)) this._splashes = [];
const idx = this.index();
for (let i = 0; i < this.maxItems(); i++) {
if (i === idx) this._hoverAlphas[i] = Math.min(1, (this._hoverAlphas[i] || 0) + 0.08);
else this._hoverAlphas[i] = Math.max(0, (this._hoverAlphas[i] || 0) - 0.05);
}
if (idx !== this._lastIndex) { this._lastIndex = idx; this._selectedFlash = 15; this.refresh(); }
if (this._selectedFlash > 0) { this._selectedFlash--; this.refresh(); }
if (this._animFrame % 4 === 0) this.refresh();
}
_refreshBack() {}
refresh() {
if (!this.contents) return;
if (!Array.isArray(this._splashes)) this._splashes = [];
if (!Array.isArray(this._hoverAlphas)) this._hoverAlphas = new Array(10).fill(0);
this.contents.clear();
const ctx = getCtx(this.contents);
if (ctx) drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'left', barSize: 8, splashes: this._splashes });
super.refresh();
}
drawItem(index) {
const rect = this.itemLineRect(index);
const ctx = getCtx(this.contents);
if (!ctx) { super.drawItem(index); return; }
const name = this.commandName(index);
const enabled = this.isCommandEnabled(index);
const isSelected = (index === this.index());
const hoverAlpha = (this._hoverAlphas && this._hoverAlphas[index]) || 0;
const skew = 12, pad = 4;
drawSkewedRect(ctx, rect.x + BAR_WIDTH + pad, rect.y + pad,
rect.width - BAR_WIDTH - pad * 2, rect.height - pad * 2, skew, '#000', 0.25);
drawSkewedRect(ctx, rect.x + BAR_WIDTH + pad + 2, rect.y + pad,
rect.width - BAR_WIDTH - pad * 2 - 2, rect.height - pad * 2, skew,
ACCENT, enabled ? (0.15 + hoverAlpha * 0.25) : 0.07);
if (isSelected) {
const fb = this._selectedFlash > 0 ? (this._selectedFlash / 15) * 0.3 : 0;
drawSkewedRect(ctx, rect.x + BAR_WIDTH + pad + 2, rect.y + pad,
rect.width - BAR_WIDTH - pad * 2 - 2, rect.height - pad * 2, skew, COLOR1, 0.55 + fb);
if (this._selectedFlash > 8)
drawPaintSplash(ctx, rect.x + rect.width - 30, rect.y + rect.height / 2,
25 + (this._selectedFlash / 15) * 20, PAINT, 0.6 + (this._selectedFlash / 15) * 0.3);
ctx.save();
ctx.fillStyle = PAINT;
ctx.globalAlpha = 0.9 + Math.sin(this._animFrame * 0.15) * 0.1;
ctx.beginPath();
ctx.moveTo(rect.x + BAR_WIDTH + pad - 2, rect.y + rect.height / 2 - 8);
ctx.lineTo(rect.x + BAR_WIDTH + pad + 10, rect.y + rect.height / 2);
ctx.lineTo(rect.x + BAR_WIDTH + pad - 2, rect.y + rect.height / 2 + 8);
ctx.closePath(); ctx.fill(); ctx.restore();
}
const textX = rect.x + BAR_WIDTH + pad + skew + 12;
const textY = rect.y + rect.height / 2;
const fontSize = isSelected ? 22 : 19;
ctx.save();
ctx.font = `bold ${fontSize}px "Arial Black", Arial, sans-serif`;
ctx.textBaseline = 'middle';
ctx.fillStyle = '#000'; ctx.globalAlpha = 0.6;
ctx.fillText(name.toUpperCase(), textX + 2, textY + 2);
ctx.fillStyle = !enabled ? '#888' : ACCENT;
ctx.globalAlpha = !enabled ? 0.5 : isSelected ? 1 : 0.85;
ctx.fillText(name.toUpperCase(), textX, textY);
if (isSelected) {
const tw = ctx.measureText(name.toUpperCase()).width;
ctx.fillStyle = PAINT; ctx.globalAlpha = 0.9;
ctx.fillRect(textX, textY + fontSize / 2 + 2, tw * 0.6, 2);
}
ctx.restore();
}
_updateCursor() { this.setCursorRect(0, 0, 0, 0); }
}
// ============================================================
// WINDOW_MENU_STATUS
// ============================================================
class Window_UniqueMenuStatus extends Window_MenuStatus {
constructor(rect) {
super(rect);
this.opacity = 0; this.frameVisible = false;
}
refresh() {
if (!this.contents) return;
this.contents.clear();
const ctx = getCtx(this.contents);
if (ctx) {
drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'top', barSize: 5, speedLines: false });
p5Text(ctx, 'PARTY', 12, 18, { size: 13, color: COLOR1 });
ctx.save(); ctx.fillStyle = PAINT; ctx.globalAlpha = 0.6;
ctx.fillRect(12, 30, 50, 2); ctx.restore();
}
super.refresh();
}
drawItem(index) {
const actor = $gameParty.members()[index];
if (!actor) return;
const rect = this.itemRect(index);
const ctx = getCtx(this.contents);
if (!ctx) { super.drawItem(index); return; }
const pad = 5;
const faceSize = 58;
// Fond oblique item
drawSkewedRect(ctx, rect.x + pad, rect.y + pad,
rect.width - pad * 2, rect.height - pad * 2, 6, ACCENT, 0.07);
// Portrait
this.drawFace(actor.faceName(), actor.faceIndex(),
rect.x + rect.width - faceSize - pad - 2,
rect.y + pad,
faceSize, rect.height - pad * 2);
// Nom + niveau
const infoX = rect.x + pad + 10;
p5Text(ctx, actor.name().toUpperCase(), infoX, rect.y + pad + 14, { size: 15 });
p5Text(ctx, `LV. ${actor.level}`, infoX, rect.y + pad + 32,
{ size: 11, color: PAINT, shadow: false });
// Zone barres — occupe le bas de la carte
const barW = rect.width - faceSize - pad * 2 - 20;
const barX = infoX;
const midY = rect.y + rect.height / 2 + 6;
const hpRatio = actor.mhp > 0 ? actor.hp / actor.mhp : 0;
const mpRatio = actor.mmp > 0 ? actor.mp / actor.mmp : 0;
const hpColor = hpRatio > 0.5 ? '#00DD55' : hpRatio > 0.25 ? '#FFAA00' : COLOR1;
const mpColor = '#5599FF';
// --- HP ---
// Label HP
ctx.save();
ctx.font = 'bold 10px "Arial Black", Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = hpColor; ctx.globalAlpha = 1;
ctx.fillText('HP', barX, midY);
// Valeur HP
ctx.font = '10px Arial';
ctx.fillStyle = ACCENT; ctx.globalAlpha = 0.9;
ctx.fillText(`${actor.hp}/${actor.mhp}`, barX + 22, midY);
ctx.restore();
// Barre HP
drawStatBar(ctx, barX, midY + 8, barW, 6, hpRatio, hpColor);
// --- MP ---
const mpRowY = midY + 22;
ctx.save();
ctx.font = 'bold 10px "Arial Black", Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = mpColor; ctx.globalAlpha = 1;
ctx.fillText('MP', barX, mpRowY);
ctx.font = '10px Arial';
ctx.fillStyle = ACCENT; ctx.globalAlpha = 0.9;
ctx.fillText(`${actor.mp}/${actor.mmp}`, barX + 22, mpRowY);
ctx.restore();
// Barre MP
drawStatBar(ctx, barX, mpRowY + 8, barW, 6, mpRatio, mpColor);
}
}
// ============================================================
// GOLD WINDOW
// ============================================================
Window_Gold.prototype.refresh = function () {
// PATCH — GOLD BOOST VISIBILITY
Window_Gold.prototype.refresh = function () {
if (!this.contents) return;
this.contents.clear();
const ctx = this.contents.context;
const w = this.contentsWidth();
const h = this.contentsHeight();
// Fond plus contrasté
drawBg(ctx, w, h, {
barSide: 'top',
barSize: 8,
barColor: PAINT,
bgAlpha: 0.96,
speedLines: false
});
// Badge GOLD plus large
ctx.save();
ctx.fillStyle = COLOR1;
ctx.globalAlpha = 1;
ctx.fillRect(6, 4, 70, 22);
ctx.font = 'bold 13px "Arial Black", Arial';
ctx.fillStyle = ACCENT;
ctx.textBaseline = 'middle';
ctx.fillText('GOLD', 12, 15);
ctx.restore();
// Séparateur
ctx.save();
ctx.fillStyle = PAINT;
ctx.globalAlpha = 0.6;
ctx.fillRect(6, 30, w - 12, 2);
ctx.restore();
// Icône pièce — plus brillante
const cy = 30 + Math.floor((h - 30) / 2);
const iconR = 20;
const iconX = 16 + iconR;
ctx.save();
const grad = ctx.createRadialGradient(iconX - 4, cy - 4, 1, iconX, cy, iconR);
grad.addColorStop(0, '#FFFDE7');
grad.addColorStop(0.4, '#FFD700');
grad.addColorStop(1, '#8B6B00');
ctx.beginPath();
ctx.arc(iconX, cy, iconR, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
// Contour lumineux
ctx.strokeStyle = '#FFF8B0';
ctx.lineWidth = 2;
ctx.globalAlpha = 0.9;
ctx.stroke();
// Lettre G
ctx.fillStyle = '#1A1A1A';
ctx.font = 'bold 15px "Arial Black", Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('G', iconX, cy + 1);
ctx.restore();
// Valeur + halo
const value = $gameParty.gold().toLocaleString();
const unit = TextManager.currencyUnit;
const startX = iconX + iconR + 10;
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
// Halo
ctx.font = 'bold 32px "Arial Black", Arial';
ctx.fillStyle = PAINT;
ctx.globalAlpha = 0.25;
ctx.fillText(value, startX + 3, cy + 3);
// Texte principal
ctx.globalAlpha = 1;
ctx.fillStyle = PAINT;
ctx.fillText(value, startX, cy);
// Badge unité
const vw = ctx.measureText(value).width;
const ux = startX + vw + 14;
const uw = ctx.measureText(unit).width + 18;
const uh = 30;
ctx.fillStyle = COLOR1;
ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.moveTo(ux + 6, cy - uh / 2);
ctx.lineTo(ux + uw + 6, cy - uh / 2);
ctx.lineTo(ux + uw - 6, cy + uh / 2);
ctx.lineTo(ux - 6, cy + uh / 2);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = PAINT;
ctx.lineWidth = 2;
ctx.globalAlpha = 0.7;
ctx.stroke();
ctx.fillStyle = ACCENT;
ctx.globalAlpha = 1;
ctx.fillText(unit, ux + (uw - ctx.measureText(unit).width) / 2, cy + 1);
ctx.restore();
};
if (!this.contents) return;
this.contents.clear();
const ctx = getCtx(this.contents);
if (!ctx) { Window_Base.prototype.refresh.call(this); return; }
const w = this.contentsWidth();
const h = this.contentsHeight();
// Fond sombre + barre dorée en haut
drawBg(ctx, w, h, {
barSide: 'top', barSize: 6, barColor: PAINT,
speedLines: false, splashes: []
});
// Badge "GOLD" en haut à gauche
ctx.save();
ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.fillRect(6, 4, 46, 18);
ctx.font = 'bold 11px "Arial Black", Arial, sans-serif';
ctx.fillStyle = ACCENT; ctx.globalAlpha = 1;
ctx.textBaseline = 'middle'; ctx.textAlign = 'left';
ctx.fillText('GOLD', 9, 13);
ctx.restore();
// Séparateur
ctx.save(); ctx.fillStyle = PAINT; ctx.globalAlpha = 0.5;
ctx.fillRect(6, 26, w - 12, 1); ctx.restore();
// Centre vertical de la zone sous le séparateur
const cy = 26 + Math.floor((h - 26) / 2);
// Icône pièce
const iconR = Math.min(18, Math.floor((h - 30) / 2) - 2);
const iconX = 8 + iconR;
const iconY = cy;
ctx.save();
const grad = ctx.createRadialGradient(iconX - 3, iconY - 3, 1, iconX, iconY, iconR);
grad.addColorStop(0, '#FFF59D');
grad.addColorStop(0.4, '#FFD700');
grad.addColorStop(1, '#7B5E00');
ctx.beginPath(); ctx.arc(iconX, iconY, iconR, 0, Math.PI * 2);
ctx.fillStyle = grad; ctx.fill();
ctx.strokeStyle = '#FFE566'; ctx.lineWidth = 1.5; ctx.globalAlpha = 0.9; ctx.stroke();
ctx.fillStyle = '#1A1A1A'; ctx.globalAlpha = 1;
ctx.font = 'bold 13px "Arial Black", Arial';
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
ctx.fillText('G', iconX, iconY + 1);
ctx.restore();
// Valeur + unité
const value = $gameParty.gold();
const unit = TextManager.currencyUnit;
const valueStr = value.toLocaleString();
const startX = iconX + iconR + 6;
ctx.save();
ctx.textBaseline = 'middle'; ctx.textAlign = 'left';
// Ombre valeur
ctx.font = 'bold 28px "Arial Black", Arial, sans-serif';
ctx.fillStyle = '#000000'; ctx.globalAlpha = 0.55;
ctx.fillText(valueStr, startX + 2, cy + 2);
// Valeur dorée
ctx.fillStyle = PAINT; ctx.globalAlpha = 1;
ctx.fillText(valueStr, startX, cy);
// Unité "Mo" — badge rouge oblique bien lisible
const vw = ctx.measureText(valueStr).width;
const ux = startX + vw + 10;
ctx.font = 'bold 20px "Arial Black", Arial, sans-serif';
const uw = ctx.measureText(unit).width + 14;
const uh = 28;
// Fond oblique rouge
ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.moveTo(ux + 5, cy - uh / 2);
ctx.lineTo(ux + uw + 5, cy - uh / 2);
ctx.lineTo(ux + uw - 5, cy + uh / 2);
ctx.lineTo(ux - 5, cy + uh / 2);
ctx.closePath(); ctx.fill();
// Contour PAINT
ctx.strokeStyle = PAINT; ctx.lineWidth = 1.5; ctx.globalAlpha = 0.7;
ctx.beginPath();
ctx.moveTo(ux + 5, cy - uh / 2);
ctx.lineTo(ux + uw + 5, cy - uh / 2);
ctx.lineTo(ux + uw - 5, cy + uh / 2);
ctx.lineTo(ux - 5, cy + uh / 2);
ctx.closePath(); ctx.stroke();
// Texte unité blanc
ctx.fillStyle = ACCENT; ctx.globalAlpha = 1;
ctx.fillText(unit, ux + (uw - ctx.measureText(unit).width) / 2, cy + 1);
ctx.restore();
// Décoration coin
drawPaintSplash(ctx, w - 12, h - 8, 10, PAINT, 0.06);
ctx.save(); ctx.strokeStyle = COLOR1; ctx.globalAlpha = 0.15; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(w - 30, 0); ctx.lineTo(w + 2, h);
ctx.stroke(); ctx.restore();
};
// ============================================================
// SCENE_MENU — layout + création fenêtres
// ============================================================
Scene_Menu.prototype.commandWindowRect = function () {
const ww = Math.floor(Graphics.boxWidth * 0.42);
const wh = this.mainAreaHeight() - GOLD_H - 4;
return new Rectangle(0, this.mainAreaTop(), ww, wh);
};
Scene_Menu.prototype.statusWindowRect = function () {
const cx = Math.floor(Graphics.boxWidth * 0.42) + 4;
return new Rectangle(cx, this.mainAreaTop(),
Graphics.boxWidth - cx, this.mainAreaHeight());
};
Scene_Menu.prototype.goldWindowRect = function () {
const ww = Math.floor(Graphics.boxWidth * 0.42);
const wy = this.mainAreaBottom() - GOLD_H;
return new Rectangle(0, wy, ww, GOLD_H);
};
Scene_Menu.prototype.createCommandWindow = function () {
const rect = this.commandWindowRect();
this._commandWindow = new Window_UniqueMenuCommand(rect);
this._commandWindow.setHandler('item', this.commandItem.bind(this));
this._commandWindow.setHandler('skill', this.commandPersonal.bind(this));
this._commandWindow.setHandler('equip', this.commandPersonal.bind(this));
this._commandWindow.setHandler('status', this.commandPersonal.bind(this));
this._commandWindow.setHandler('formation', this.commandFormation.bind(this));
this._commandWindow.setHandler('options', this.commandOptions.bind(this));
this._commandWindow.setHandler('save', this.commandSave.bind(this));
this._commandWindow.setHandler('gameEnd', this.commandGameEnd.bind(this));
this._commandWindow.setHandler('cancel', this._startCloseTransition.bind(this));
this.addWindow(this._commandWindow);
};
Scene_Menu.prototype.createStatusWindow = function () {
this._statusWindow = new Window_UniqueMenuStatus(this.statusWindowRect());
this.addWindow(this._statusWindow);
};
const _createGold = Scene_Menu.prototype.createGoldWindow;
Scene_Menu.prototype.createGoldWindow = function () {
_createGold.call(this);
if (this._goldWindow) { this._goldWindow.opacity = 0; this._goldWindow.frameVisible = false; }
};
// Supprimer "^:Select / X:Back" — on cache la fenêtre d'info de formation
const _Scene_Menu_create = Scene_Menu.prototype.create;
Scene_Menu.prototype.create = function () {
_Scene_Menu_create.call(this);
// Masquer toute fenêtre d'info/help éventuellement créée
if (this._helpWindow) {
this._helpWindow.opacity = 0;
this._helpWindow.frameVisible = false;
this._helpWindow.visible = false;
}
};
// Empêcher la mise à jour du texte d'aide (^:Select, X:Back)
Window_UniqueMenuStatus.prototype.updateHelp = function () {};
// ============================================================
// TRANSITIONS MENU — Sweep SF6 stylisé + slide des fenêtres
// ============================================================
const _Scene_Menu_start = Scene_Menu.prototype.start;
Scene_Menu.prototype.start = function () {
_Scene_Menu_start.call(this);
this._menuState = 'opening'; // 'opening' | 'idle' | 'closing'
this._transTimer = 0;
// Positions de départ pour le slide
this._commandWindow.x = -this._commandWindow.width;
this._statusWindow.x = Graphics.boxWidth;
this._goldWindow.y = Graphics.boxHeight;
// Sprite de sweep par-dessus tout
this._sweepSprite = new Sprite(new Bitmap(Graphics.width, Graphics.height));
this._sweepSprite.z = 100;
this.addChild(this._sweepSprite);
this._drawSweepOpen(0);
};
// Sweep d'ouverture : rideau rouge qui part de la gauche
Scene_Menu.prototype._drawSweepOpen = function (progress) {
if (!this._sweepSprite) return;
const bmp = this._sweepSprite.bitmap; bmp.clear();
const ctx = getCtx(bmp); if (!ctx) return;
const W = Graphics.width, H = Graphics.height;
const sweepX = W * progress;
// Flash blanc très bref au tout début (impact)
if (progress < 0.08) {
ctx.save(); ctx.fillStyle = '#ffffff';
ctx.globalAlpha = (0.08 - progress) / 0.08 * 0.6;
ctx.fillRect(0, 0, W, H); ctx.restore();
}
// Rideau principal rouge qui recule vers la gauche
if (sweepX < W) {
ctx.save(); ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(sweepX + 50, 0);
ctx.lineTo(sweepX - 25, H); ctx.lineTo(0, H);
ctx.closePath(); ctx.fill(); ctx.restore();
// Bande sombre derrière le rideau (épaisseur du bord)
ctx.save(); ctx.fillStyle = '#000'; ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(sweepX - 10, 0); ctx.lineTo(sweepX + 50, 0);
ctx.lineTo(sweepX - 5, H); ctx.lineTo(sweepX - 50, H);
ctx.closePath(); ctx.fill(); ctx.restore();
// Ligne lumineuse sur le bord tranchant
ctx.save(); ctx.strokeStyle = PAINT; ctx.lineWidth = 3; ctx.globalAlpha = 0.9;
ctx.beginPath();
ctx.moveTo(sweepX + 50, 0); ctx.lineTo(sweepX - 25, H);
ctx.stroke(); ctx.restore();
}
// Scanlines sur le rideau (style rétro)
if (sweepX > 10) {
ctx.save(); ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.globalAlpha = 0.12;
for (let y = 0; y < H; y += 4) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(Math.min(sweepX + 50, W), y);
ctx.stroke();
}
ctx.restore();
}
// Splashes sur le bord tranchant
if (progress > 0.05 && progress < 0.95) {
for (let i = 0; i < 6; i++) {
drawPaintSplash(ctx,
sweepX + (Math.random() - 0.5) * 70,
(i / 5) * H + (Math.random() - 0.5) * 60,
8 + Math.random() * 22, PAINT,
0.55 * (1 - progress));
}
}
// Texte "MENU" avec double ombre style P5
if (progress < 0.65) {
const label = 'MENU';
const alpha = Math.max(0, (0.65 - progress) / 0.65);
const centerX = sweepX * 0.45;
ctx.save();
ctx.font = 'bold 72px "Arial Black", Arial, sans-serif';
ctx.textBaseline = 'middle'; ctx.textAlign = 'center';
// Ombre rouge décalée (effet P5 signature)
ctx.fillStyle = COLOR1; ctx.globalAlpha = alpha * 0.8;
ctx.fillText(label, centerX + 6, H / 2 + 6);
// Ombre dorée
ctx.fillStyle = PAINT; ctx.globalAlpha = alpha * 0.6;
ctx.fillText(label, centerX + 3, H / 2 + 3);
// Texte principal blanc
ctx.fillStyle = ACCENT; ctx.globalAlpha = alpha;
ctx.fillText(label, centerX, H / 2);
ctx.restore();
}
};
// Sweep de fermeture : rideau rouge vient de la droite
Scene_Menu.prototype._drawSweepClose = function (progress) {
if (!this._sweepSprite) return;
const bmp = this._sweepSprite.bitmap; bmp.clear();
const ctx = getCtx(bmp); if (!ctx) return;
const W = Graphics.width, H = Graphics.height;
const sweepX = W * (1 - progress); // bord gauche du rideau, avance de droite à gauche
// Rideau principal rouge
ctx.save(); ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(sweepX - 50, 0); ctx.lineTo(W, 0);
ctx.lineTo(W, H); ctx.lineTo(sweepX + 25, H);
ctx.closePath(); ctx.fill(); ctx.restore();
// Bande sombre sur le bord avant (impact visuel)
ctx.save(); ctx.fillStyle = '#000'; ctx.globalAlpha = 0.45;
ctx.beginPath();
ctx.moveTo(sweepX - 50, 0); ctx.lineTo(sweepX + 10, 0);
ctx.lineTo(sweepX + 50, H); ctx.lineTo(sweepX - 10, H);
ctx.closePath(); ctx.fill(); ctx.restore();
// Ligne lumineuse sur le bord
ctx.save(); ctx.strokeStyle = PAINT; ctx.lineWidth = 3; ctx.globalAlpha = 0.85;
ctx.beginPath();
ctx.moveTo(sweepX - 50, 0); ctx.lineTo(sweepX + 25, H);
ctx.stroke(); ctx.restore();
// Scanlines
ctx.save(); ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.globalAlpha = 0.1;
for (let y = 0; y < H; y += 4) {
ctx.beginPath();
ctx.moveTo(Math.max(0, sweepX - 50), y); ctx.lineTo(W, y);
ctx.stroke();
}
ctx.restore();
// Splashes sur le bord
if (progress > 0.05 && progress < 0.95) {
for (let i = 0; i < 6; i++) {
drawPaintSplash(ctx,
sweepX + (Math.random() - 0.5) * 70,
(i / 5) * H + (Math.random() - 0.5) * 60,
8 + Math.random() * 22, PAINT,
0.55 * progress);
}
}
// Texte "MENU" avec triple ombre style P5
if (progress > 0.40) {
const label = 'MENU';
const alpha = Math.min(1, (progress - 0.40) / 0.4);
const cx = sweepX + (W - sweepX) * 0.5;
ctx.save();
ctx.font = 'bold 72px "Arial Black", Arial, sans-serif';
ctx.textBaseline = 'middle'; ctx.textAlign = 'center';
ctx.fillStyle = COLOR1; ctx.globalAlpha = alpha * 0.8;
ctx.fillText(label, cx + 6, H / 2 + 6);
ctx.fillStyle = PAINT; ctx.globalAlpha = alpha * 0.6;
ctx.fillText(label, cx + 3, H / 2 + 3);
ctx.fillStyle = ACCENT; ctx.globalAlpha = alpha;
ctx.fillText(label, cx, H / 2);
ctx.restore();
}
// Flash blanc à la fin (écran couvert)
if (progress > 0.92) {
ctx.save(); ctx.fillStyle = '#fff';
ctx.globalAlpha = (progress - 0.92) / 0.08 * 0.35;
ctx.fillRect(0, 0, W, H); ctx.restore();
}
};
Scene_Menu.prototype._startCloseTransition = function () {
if (this._menuState === 'closing') return;
this._menuState = 'closing';
this._transTimer = 0;
if (!this._sweepSprite) {
this._sweepSprite = new Sprite(new Bitmap(Graphics.width, Graphics.height));
this._sweepSprite.z = 100;
this.addChild(this._sweepSprite);
}
};
const _Scene_Menu_update = Scene_Menu.prototype.update;
Scene_Menu.prototype.update = function () {
_Scene_Menu_update.call(this);
this._transTimer++;
const speed = TRANS_SPEED;
if (this._menuState === 'opening') {
const p = Math.min(1, this._transTimer / speed);
this._drawSweepOpen(p);
// Slide des fenêtres avec easing
const ease = t => 1 - Math.pow(1 - t, 3); // ease out cubic
const ep = ease(p);
const cmdTargetX = 0;
const stTargetX = Math.floor(Graphics.boxWidth * 0.42) + 4;
const gdTargetY = this.mainAreaBottom() - GOLD_H;
this._commandWindow.x = Math.round(-this._commandWindow.width * (1 - ep));
this._statusWindow.x = Math.round(Graphics.boxWidth - (Graphics.boxWidth - stTargetX) * ep);
this._goldWindow.y = Math.round(Graphics.boxHeight - GOLD_H * ep);
if (p >= 1) {
this._menuState = 'idle';
this._commandWindow.x = cmdTargetX;
this._statusWindow.x = stTargetX;
this._goldWindow.y = gdTargetY;
this.removeChild(this._sweepSprite);
this._sweepSprite = null;
}
} else if (this._menuState === 'closing') {
const p = Math.min(1, this._transTimer / speed);
this._drawSweepClose(p);
if (p >= 1) {
this._menuState = 'done';
this.popScene();
}
}
};
// ============================================================
// SWEEP SOUS-SCÈNES (Items, Skill, Equip, Status, Save…)
// ============================================================
function addSceneSweep(SceneClass) {
const _start = SceneClass.prototype.start;
const _update = SceneClass.prototype.update;
SceneClass.prototype.start = function () {
_start.call(this);
this._subTimer = 0; this._subDone = false;
this._subSprite = new Sprite(new Bitmap(Graphics.width, Graphics.height));
this._subSprite.z = 100;
this.addChild(this._subSprite);
_drawSubSweep(this._subSprite, 0);
};
SceneClass.prototype.update = function () {
_update.call(this);
if (this._subSprite && !this._subDone) {
this._subTimer++;
const p = Math.min(1, this._subTimer / Math.round(TRANS_SPEED * 1.1));
_drawSubSweep(this._subSprite, p);
if (p >= 1) {
this._subDone = true;
this.removeChild(this._subSprite); this._subSprite = null;
}
}
};
}
function _drawSubSweep(sprite, progress) {
const bmp = sprite.bitmap; bmp.clear();
const ctx = getCtx(bmp); if (!ctx) return;
const W = Graphics.width, H = Graphics.height;
const sweepX = W * progress;
// Flash bref au début
if (progress < 0.06) {
ctx.save(); ctx.fillStyle = COLOR1;
ctx.globalAlpha = (0.06 - progress) / 0.06 * 0.5;
ctx.fillRect(0, 0, W, H); ctx.restore();
}
// Rideau sombre principal
ctx.save(); ctx.fillStyle = COLOR2; ctx.globalAlpha = 0.97;
ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(sweepX + 35, 0);
ctx.lineTo(sweepX - 18, H); ctx.lineTo(0, H);
ctx.closePath(); ctx.fill(); ctx.restore();
// Bande rouge sur le bord tranchant
ctx.save(); ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.9;
ctx.beginPath();
ctx.moveTo(sweepX + 20, 0); ctx.lineTo(sweepX + 35, 0);
ctx.lineTo(sweepX - 4, H); ctx.lineTo(sweepX - 18, H);
ctx.closePath(); ctx.fill(); ctx.restore();
// Ligne lumineuse PAINT
ctx.save(); ctx.strokeStyle = PAINT; ctx.lineWidth = 2; ctx.globalAlpha = 0.7;
ctx.beginPath();
ctx.moveTo(sweepX + 35, 0); ctx.lineTo(sweepX - 18, H);
ctx.stroke(); ctx.restore();
// Barre rouge verticale à gauche
ctx.save(); ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.fillRect(0, 0, 5, H * Math.max(0, 1.05 - progress));
ctx.restore();
// Splashes
if (progress > 0.05 && progress < 0.90) {
for (let i = 0; i < 4; i++) {
drawPaintSplash(ctx,
sweepX + (Math.random() - 0.5) * 50,
(i / 3) * H + (Math.random() - 0.5) * 50,
5 + Math.random() * 14, PAINT,
0.45 * (1 - progress));
}
}
}
[Scene_Item, Scene_Skill, Scene_Equip, Scene_Status,
Scene_Save, Scene_Options, Scene_GameEnd].forEach(addSceneSweep);
// ============================================================
// FORMATION — corrigée et fonctionnelle
// ============================================================
Scene_Menu.prototype.commandFormation = function () {
this._commandWindow.deactivate();
this._statusWindow.setFormationMode(true);
this._statusWindow.selectLast();
this._statusWindow.activate();
this._statusWindow.setHandler('ok', this.onFormationOk.bind(this));
this._statusWindow.setHandler('cancel', this.onFormationCancel.bind(this));
};
Scene_Menu.prototype.onFormationOk = function () {
const index = this._statusWindow.index();
const pendingIndex = this._statusWindow.pendingIndex();
if (pendingIndex >= 0) {
$gameParty.swapOrder(index, pendingIndex);
this._statusWindow.setPendingIndex(-1);
this._statusWindow.redrawItem(index);
this._statusWindow.redrawItem(pendingIndex);
this._statusWindow.activate();
} else {
this._statusWindow.setPendingIndex(index);
this._statusWindow.activate();
}
};
Scene_Menu.prototype.onFormationCancel = function () {
if (this._statusWindow.pendingIndex() >= 0) {
this._statusWindow.setPendingIndex(-1);
this._statusWindow.activate();
} else {
this._statusWindow.deactivate();
this._statusWindow.setFormationMode(false);
this._commandWindow.activate();
}
};
console.log('[UniqueMenu_MZ v1.7] Transitions stylisées + Formation corrigée');
// PATCH — GOLD ULTRA VISIBLE
Window_Gold.prototype.refresh = function () {
if (!this.contents) return;
this.contents.clear();
const ctx = this.contents.context;
const w = this.contentsWidth();
const h = this.contentsHeight();
// Fond très contrasté
ctx.save();
const gradBg = ctx.createLinearGradient(0, 0, w, h);
gradBg.addColorStop(0, "#3A2A00");
gradBg.addColorStop(1, "#1A1200");
ctx.fillStyle = gradBg;
ctx.globalAlpha = 0.95;
ctx.fillRect(0, 0, w, h);
ctx.restore();
// Bande dorée intense
ctx.save();
const barGrad = ctx.createLinearGradient(0, 0, w, 0);
barGrad.addColorStop(0, "#FFD700");
barGrad.addColorStop(1, "#FFEA8A");
ctx.fillStyle = barGrad;
ctx.fillRect(0, 0, w, 10);
ctx.restore();
// Badge GOLD très visible
ctx.save();
ctx.fillStyle = COLOR1;
ctx.globalAlpha = 1;
ctx.fillRect(10, 14, 90, 28);
ctx.font = 'bold 15px "Arial Black", Arial';
ctx.fillStyle = ACCENT;
ctx.textBaseline = 'middle';
ctx.fillText('GOLD', 20, 28);
ctx.restore();
// Icône pièce — version brillante
const cy = h / 2 + 10;
const iconR = 22;
const iconX = 120;
ctx.save();
const coinGrad = ctx.createRadialGradient(iconX - 6, cy - 6, 1, iconX, cy, iconR);
coinGrad.addColorStop(0, "#FFFDE7");
coinGrad.addColorStop(0.4, "#FFD700");
coinGrad.addColorStop(1, "#8B6B00");
ctx.beginPath();
ctx.arc(iconX, cy, iconR, 0, Math.PI * 2);
ctx.fillStyle = coinGrad;
ctx.fill();
// Contour néon
ctx.strokeStyle = "#FFF8B0";
ctx.lineWidth = 3;
ctx.globalAlpha = 0.9;
ctx.stroke();
// Lettre G
ctx.fillStyle = "#1A1A1A";
ctx.font = 'bold 17px "Arial Black", Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('G', iconX, cy + 1);
ctx.restore();
// Valeur — énorme + halo
const value = $gameParty.gold().toLocaleString();
const unit = TextManager.currencyUnit;
const startX = iconX + iconR + 20;
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
// Halo
ctx.font = 'bold 40px "Arial Black", Arial';
ctx.fillStyle = PAINT;
ctx.globalAlpha = 0.25;
ctx.fillText(value, startX + 4, cy + 4);
// Texte principal
ctx.globalAlpha = 1;
ctx.fillStyle = PAINT;
ctx.fillText(value, startX, cy);
// Badge unité
const vw = ctx.measureText(value).width;
const ux = startX + vw + 20;
const uw = ctx.measureText(unit).width + 22;
const uh = 34;
ctx.fillStyle = COLOR1;
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(ux + 8, cy - uh / 2);
ctx.lineTo(ux + uw + 8, cy - uh / 2);
ctx.lineTo(ux + uw - 8, cy + uh / 2);
ctx.lineTo(ux - 8, cy + uh / 2);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = PAINT;
ctx.lineWidth = 2;
ctx.globalAlpha = 0.8;
ctx.stroke();
ctx.fillStyle = ACCENT;
ctx.globalAlpha = 1;
ctx.fillText(unit, ux + (uw - ctx.measureText(unit).width) / 2, cy + 1);
ctx.restore();
};
// PATCH — GOLD FIX (remove red badge + move money up)
Window_Gold.prototype.refresh = function () {
if (!this.contents) return;
this.contents.clear();
const ctx = this.contents.context;
const w = this.contentsWidth();
const h = this.contentsHeight();
// Fond sombre propre
drawBg(ctx, w, h, {
barSide: 'top',
barSize: 6,
barColor: PAINT,
speedLines: false,
bgAlpha: 0.92
});
// Position verticale corrigée
const cy = Math.floor(h / 2); // centré verticalement
// Icône pièce
const iconR = 20;
const iconX = 20 + iconR;
ctx.save();
const grad = ctx.createRadialGradient(iconX - 4, cy - 4, 1, iconX, cy, iconR);
grad.addColorStop(0, "#FFFDE7");
grad.addColorStop(0.4, "#FFD700");
grad.addColorStop(1, "#8B6B00");
ctx.beginPath();
ctx.arc(iconX, cy, iconR, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
ctx.strokeStyle = "#FFF8B0";
ctx.lineWidth = 2;
ctx.globalAlpha = 0.9;
ctx.stroke();
ctx.fillStyle = "#1A1A1A";
ctx.font = 'bold 15px "Arial Black", Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('G', iconX, cy + 1);
ctx.restore();
// Montant
const value = $gameParty.gold().toLocaleString();
const unit = TextManager.currencyUnit;
const startX = iconX + iconR + 16;
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
ctx.font = 'bold 26px "Arial Black", Arial';
ctx.fillStyle = PAINT;
ctx.globalAlpha = 1;
ctx.fillText(value, startX, cy);
// Unité
const vw = ctx.measureText(value).width;
ctx.font = 'bold 16px "Arial Black", Arial';
ctx.fillStyle = COLOR1;
ctx.globalAlpha = 0.9;
ctx.fillText(unit, startX + vw + 12, cy);
ctx.restore();
};
// PATCH — GOLD FIX v2 (higher + styled unit)
Window_Gold.prototype.refresh = function () {
if (!this.contents) return;
this.contents.clear();
const ctx = this.contents.context;
const w = this.contentsWidth();
const h = this.contentsHeight();
// Fond sombre propre
drawBg(ctx, w, h, {
barSide: 'top',
barSize: 6,
barColor: PAINT,
speedLines: false,
bgAlpha: 0.92
});
// Position verticale remontée
const cy = Math.floor(h / 2) - 6;
// Icône pièce
const iconR = 15;
const iconX = 15 + iconR;
ctx.save();
const grad = ctx.createRadialGradient(iconX - 4, cy - 4, 1, iconX, cy, iconR);
grad.addColorStop(0, "#FFFDE7");
grad.addColorStop(0.4, "#FFD700");
grad.addColorStop(1, "#8B6B00");
ctx.beginPath();
ctx.arc(iconX, cy, iconR, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
ctx.strokeStyle = "#FFF8B0";
ctx.lineWidth = 2;
ctx.globalAlpha = 0.9;
ctx.stroke();
ctx.fillStyle = "#1A1A1A";
ctx.font = 'bold 15px "Arial Black", Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('G', iconX, cy + 1);
ctx.restore();
// Montant
const value = $gameParty.gold().toLocaleString();
const unit = TextManager.currencyUnit;
const startX = iconX + iconR + 16;
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
ctx.font = 'bold 26px "Arial Black", Arial';
ctx.fillStyle = PAINT;
ctx.globalAlpha = 1;
ctx.fillText(value, startX, cy);
// Badge unité stylé (Mo)
const vw = ctx.measureText(value).width;
const ux = startX + vw + 16;
const uw = ctx.measureText(unit).width + 22;
const uh = 26;
drawSkewedRect(ctx, ux, cy - uh / 2, uw, uh, 6, COLOR1, 0.95);
ctx.font = 'bold 14px "Arial Black", Arial';
ctx.fillStyle = ACCENT;
ctx.globalAlpha = 1;
ctx.fillText(unit, ux + (uw - ctx.measureText(unit).width) / 2, cy + 1);
ctx.restore();
};
// PATCH — STATUS MODERNE SF6 (coins arrondis + glow rouge)
function drawRoundedRect(ctx, x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();
}
Window_Status.prototype.refresh = function () {
if (!this.contents || !this._actor) return;
this.contents.clear();
const ctx = getCtx(this.contents);
if (!ctx) return;
const actor = this._actor;
const W = this.contentsWidth();
const H = this.contentsHeight();
// Fond P5
drawBg(ctx, W, H, { barSide: 'left', barSize: 6, speedLines: true,
splashes: [{ x: W * 0.78, y: H * 0.2, size: 90, color: COLOR1, alpha: 0.06 }] });
// ── PORTRAIT ─────────────────────────────────────────
const faceW = 160, faceH = 160;
const faceX = 20;
const clampedFaceY = 12; // ancré en haut, toujours visible
ctx.save();
ctx.fillStyle = '#000'; ctx.globalAlpha = 0.6;
ctx.fillRect(faceX - 6, clampedFaceY - 6, faceW + 12, faceH + 12);
ctx.restore();
this.drawFace(actor.faceName(), actor.faceIndex(), faceX, clampedFaceY, faceW, faceH);
ctx.save();
ctx.strokeStyle = COLOR1; ctx.lineWidth = 3; ctx.globalAlpha = 0.9;
ctx.strokeRect(faceX - 2, clampedFaceY - 2, faceW + 4, faceH + 4);
ctx.strokeStyle = PAINT; ctx.lineWidth = 1; ctx.globalAlpha = 0.35;
ctx.strokeRect(faceX - 6, clampedFaceY - 6, faceW + 12, faceH + 12);
ctx.restore();
// ── NOM / CLASSE / NIVEAU ─────────────────────────────
const infoX = faceX + faceW + 24;
const infoY = clampedFaceY; // aligné avec le haut du portrait
p5Text(ctx, actor.name().toUpperCase(), infoX, infoY + 14, { size: 34, color: ACCENT });
p5Text(ctx, actor.currentClass().name.toUpperCase(), infoX + 2, infoY + 52,
{ size: 13, color: '#AAA', shadow: false });
const lvW = 120, lvH = 32;
drawSkewedRect(ctx, infoX, infoY + 68, lvW, lvH, 8, COLOR1, 1.0);
p5Text(ctx, 'LV.', infoX + 10, infoY + 84, { size: 11, color: '#000', shadow: false });
p5Text(ctx, String(actor.level), infoX + 38, infoY + 84, { size: 18, color: '#000', shadow: false });
// ── HP / MP / EXP ─────────────────────────────────────
const barX = infoX;
const barW = W - infoX - 24;
const hpRat = actor.mhp > 0 ? actor.hp / actor.mhp : 0;
const mpRat = actor.mmp > 0 ? actor.mp / actor.mmp : 0;
const expRat = actor.isMaxLevel() ? 1 :
(actor.currentExp() - actor.currentLevelExp()) /
Math.max(1, actor.nextLevelExp() - actor.currentLevelExp());
let rowY = infoY + 112; // juste sous le badge LV
const rowStep = 38;
p5Text(ctx, 'HP', barX, rowY, { size: 13, color: COLOR1, shadow: false });
p5Text(ctx, actor.hp + ' / ' + actor.mhp, barX + 34, rowY, { size: 13, color: '#EEE', shadow: false });
drawStatBar(ctx, barX, rowY + 16, barW, 11, hpRat, '#00E055');
rowY += rowStep;
p5Text(ctx, 'MP', barX, rowY, { size: 13, color: '#4488FF', shadow: false });
p5Text(ctx, actor.mp + ' / ' + actor.mmp, barX + 34, rowY, { size: 13, color: '#EEE', shadow: false });
drawStatBar(ctx, barX, rowY + 16, barW, 11, mpRat, '#4488FF');
rowY += rowStep;
p5Text(ctx, 'EXP', barX, rowY, { size: 13, color: PAINT, shadow: false });
p5Text(ctx, Math.floor(expRat * 100) + ' %', barX + 42, rowY, { size: 13, color: '#EEE', shadow: false });
drawStatBar(ctx, barX, rowY + 16, barW, 9, expRat, PAINT);
// ── SÉPARATEUR ────────────────────────────────────────────
const sepY = rowY + 28;
ctx.save();
ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.4;
ctx.fillRect(faceX, sepY, W - faceX - 20, 2);
ctx.restore();
// ── GRILLE DE STATS (ATK / DEF / M.ATK / M.DEF / VITESSE / CHANCE / HP MAX / MP MAX) ──
const hpColor2 = hpRat > 0.5 ? '#00DD55' : hpRat > 0.25 ? '#FFAA00' : COLOR1;
const statsData = [
{ label: 'ATK', val: actor.atk, color: '#FF6666' },
{ label: 'DEF', val: actor.def, color: '#66AAFF' },
{ label: 'M.ATK', val: actor.mat, color: '#CC66FF' },
{ label: 'M.DEF', val: actor.mdf, color: '#66CCFF' },
{ label: 'VITESSE', val: actor.agi, color: '#66FF99' },
{ label: 'CHANCE', val: actor.luk, color: PAINT },
{ label: 'HP MAX', val: actor.mhp, color: hpColor2 },
{ label: 'MP MAX', val: actor.mmp, color: '#5599FF' },
];
const gridStartY = sepY + 10;
const gridCols = 4;
const gridColW = Math.floor((W - 20) / gridCols);
const gridRowH = 46;
statsData.forEach((st, i) => {
const col = i % gridCols;
const row = Math.floor(i / gridCols);
const sx = 10 + col * gridColW;
const sy = gridStartY + row * gridRowH;
drawSkewedRect(ctx, sx + 2, sy + 3, gridColW - 4, gridRowH - 6, 4, ACCENT, 0.06);
ctx.save();
ctx.fillStyle = st.color; ctx.globalAlpha = 0.85;
ctx.fillRect(sx + 2, sy + 5, 3, gridRowH - 10);
ctx.restore();
p5Text(ctx, st.label, sx + 10, sy + gridRowH / 2 - 9, { size: 9, color: st.color, shadow: false });
p5Text(ctx, String(st.val), sx + 10, sy + gridRowH / 2 + 9, { size: 18, color: ACCENT, shadow: true });
});
// ── ÉQUIPEMENTS ───────────────────────────────────────────
const equipStartY = gridStartY + 2 * gridRowH + 12;
const equipItems = actor.equips();
const slotNames = ['ARME', 'ARMURE', 'CASQUE', 'ACCESSOIRE', 'ACC. 2'];
const eColW = Math.floor((W - 20) / 3);
equipItems.forEach((equip, i) => {
if (i >= 6) return;
const col = i % 3;
const row = Math.floor(i / 3);
const ex = 10 + col * eColW;
const ey = equipStartY + row * 32;
const slotLabel = slotNames[i] || ('SLOT ' + (i + 1));
ctx.save();
ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.18;
ctx.fillRect(ex + 2, ey + 2, eColW - 4, 27);
ctx.restore();
ctx.save();
ctx.fillStyle = PAINT; ctx.globalAlpha = 0.7;
ctx.fillRect(ex + 2, ey + 2, 3, 27);
ctx.restore();
p5Text(ctx, slotLabel, ex + 10, ey + 9, { size: 8, color: PAINT, shadow: false });
p5Text(ctx, equip ? equip.name.toUpperCase() : '—', ex + 10, ey + 20,
{ size: 10, color: equip ? ACCENT : '#555', shadow: false });
});
};
// Bloquer les méthodes natives de Window_Status qui redessinent par-dessus
Window_Status.prototype.drawBasicInfo = function() {};
Window_Status.prototype.drawParameters = function() {};
Window_Status.prototype.drawExpInfo = function() {};
Window_Status.prototype.drawEquipments = function() {};
Window_Status.prototype.drawProfile = function() {};
Window_Status.prototype.drawStatus = function() {};
// ── SCENE_STATUS — version propre sans stats ni équip ───────────────────────
// On garde uniquement Window_Status (portrait + barres HP/MP/EXP + rectangle bas)
// Toutes les fenêtres supplémentaires (params, equip, profile) sont neutralisées
// ET onActorChange est corrigé pour ne pas crasher sur les fenêtres inexistantes
// Neutraliser createProfileWindow (retourne une fenêtre factice pour éviter les crashes)
Scene_Status.prototype.createProfileWindow = function() {
// Window_Help factice 1x1 invisible — a setText() + toutes les méthodes natives
const rect = new Rectangle(-1, -1, 1, 1);
this._profileWindow = new Window_Help(rect);
this._profileWindow.opacity = 0;
this._profileWindow.contentsOpacity = 0;
this._profileWindow.visible = false;
this._profileWindow.setActor = function() {};
this.addWindow(this._profileWindow);
};
// Neutraliser les fenêtres stats/équip — elles n'existent pas mais on les simule
Scene_Status.prototype.createStatusParamsWindow = function() {
// Vraie Window_Base 1x1 invisible — toutes les méthodes héritées, aucun crash possible
const rect = new Rectangle(-1, -1, 1, 1);
this._statusParamsWindow = new Window_Base(rect);
this._statusParamsWindow.opacity = 0;
this._statusParamsWindow.visible = false;
this._statusParamsWindow.setActor = function() {};
this._statusParamsWindow.select = function() {};
this.addWindow(this._statusParamsWindow);
};
Scene_Status.prototype.createStatusEquipWindow = function() {
const rect = new Rectangle(-1, -1, 1, 1);
this._statusEquipWindow = new Window_Base(rect);
this._statusEquipWindow.opacity = 0;
this._statusEquipWindow.visible = false;
this._statusEquipWindow.setActor = function() {};
this._statusEquipWindow.select = function() {};
this.addWindow(this._statusEquipWindow);
};
// Override refreshActor — appelé par RMMZ sur chaque changement d'acteur
// On neutralise tout sauf le redraw de notre Window_Status custom
Scene_Status.prototype.refreshActor = function() {
const actor = this._actor;
if (!actor) return;
if (this._statusWindow) {
this._statusWindow.setActor(actor);
this._statusWindow.refresh();
}
// profileWindow est une Window_Help factice invisible — on ne lui passe rien
// statusParamsWindow et statusEquipWindow sont des Window_Base factices — idem
};
// onActorChange est géré par Scene_MenuBase qui appelle this.refreshActor() automatiquement.
// Scene_Status : RMMZ gère pageup/pagedown nativement pour changer d'acteur.
// On override juste start pour activer la fenêtre et permettre la navigation.
const _SceneStatus_start = Scene_Status.prototype.start;
Scene_Status.prototype.start = function() {
_SceneStatus_start.call(this);
if (this._statusWindow) {
this._statusWindow.activate();
}
};
// Window_Status occupe toute la zone principale
Scene_Status.prototype.statusWindowRect = function() {
const wx = 0;
const wy = this.mainAreaTop();
const ww = Graphics.boxWidth;
const wh = this.mainAreaHeight();
return new Rectangle(wx, wy, ww, wh);
};
// ============================================================
// WINDOW_MENUACTOR — Version sombre & propre
// ============================================================
const _WMA_init = Window_MenuActor.prototype.initialize;
Window_MenuActor.prototype.initialize = function(rect) {
_WMA_init.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
};
const _WMA_refresh = Window_MenuActor.prototype.refresh;
Window_MenuActor.prototype.refresh = function() {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) {
// Fond totalement opaque
drawBg(ctx, this.contentsWidth(), this.contentsHeight(), {
barSide: 'left',
barSize: 6,
speedLines: false,
bgAlpha: 1.0 // <-- OPAQUE
});
}
}
_WMA_refresh.call(this);
};
Window_MenuActor.prototype.drawItem = function(index) {
const actor = $gameParty.members()[index];
if (!actor) return;
const rect = this.itemRect(index);
const ctx = getCtx(this.contents);
const pad = 6;
const skew = 8;
const isSelected = (index === this.index());
// --- Overlay sombre derrière chaque case ---
ctx.save();
ctx.fillStyle = "#000";
ctx.globalAlpha = 1.00; // <-- assombrissement fort
ctx.fillRect(rect.x + pad, rect.y + pad, rect.width - pad * 2, rect.height - pad * 2);
ctx.restore();
// --- Fond oblique stylé ---
drawSkewedRect(
ctx,
rect.x + pad,
rect.y + pad,
rect.width - pad * 2,
rect.height - pad * 2,
skew,
isSelected ? COLOR1 : ACCENT,
isSelected ? 0.30 : 0.12
);
// --- Contour si sélectionné ---
if (isSelected) {
ctx.save();
ctx.strokeStyle = PAINT;
ctx.globalAlpha = 0.9;
ctx.lineWidth = 2;
ctx.strokeRect(
rect.x + pad + 2,
rect.y + pad + 2,
rect.width - pad * 2 - 4,
rect.height - pad * 2 - 4
);
ctx.restore();
}
// --- Portrait ---
const faceSize = rect.height - pad * 2 - 4;
this.drawFace(
actor.faceName(),
actor.faceIndex(),
rect.x + pad + 6,
rect.y + pad + 2,
faceSize,
faceSize
);
// --- Texte ---
const textX = rect.x + pad + faceSize + 20;
const textY = rect.y + rect.height / 2;
// Nom
p5Text(ctx, actor.name().toUpperCase(), textX, textY - 18, { size: 18, color: ACCENT });
// Classe + niveau
p5Text(ctx, actor.currentClass().name + ' – LV.' + actor.level,
textX, textY - 2, { size: 11, color: '#999', shadow: false });
// Barres HP / MP avec chiffres AU-DESSUS
const bW = rect.width - (textX - rect.x) - 24;
const hpR = actor.mhp > 0 ? actor.hp / actor.mhp : 0;
const mpR = actor.mmp > 0 ? actor.mp / actor.mmp : 0;
// HP — label + chiffres en haut, barre dessous
p5Text(ctx, 'HP', textX, textY + 14, { size: 10, color: '#00E055', shadow: false });
p5Text(ctx, actor.hp + ' / ' + actor.mhp, textX + bW, textY + 14,
{ size: 10, color: '#00E055', shadow: false, align: 'right' });
drawStatBar(ctx, textX, textY + 22, bW, 8, hpR, '#00E055');
// MP — label + chiffres en haut, barre dessous
p5Text(ctx, 'MP', textX, textY + 36, { size: 10, color: '#4488FF', shadow: false });
p5Text(ctx, actor.mp + ' / ' + actor.mmp, textX + bW, textY + 36,
{ size: 10, color: '#4488FF', shadow: false, align: 'right' });
drawStatBar(ctx, textX, textY + 44, bW, 8, mpR, '#4488FF');
};
// ============================================================
// WINDOW_SKILLTYPE — Style Persona 5 Clean
// ============================================================
Window_SkillType.prototype.drawItem = function(index) {
const rect = this.itemRect(index);
const ctx = getCtx(this.contents);
if (!ctx) return;
const name = this.commandName(index);
const isSelected = (index === this.index());
const pad = 6;
const skew = 10;
// Fond oblique
drawSkewedRect(
ctx,
rect.x + pad,
rect.y + pad,
rect.width - pad * 2,
rect.height - pad * 2,
skew,
isSelected ? COLOR1 : ACCENT,
isSelected ? 0.25 : 0.10
);
// Trait sous l’onglet sélectionné
if (isSelected) {
ctx.save();
ctx.fillStyle = PAINT;
ctx.globalAlpha = 0.9;
ctx.fillRect(rect.x + pad + 8, rect.y + rect.height - 6,
rect.width - pad * 2 - 16, 3);
ctx.restore();
}
// Texte centré
p5Text(
ctx,
name.toUpperCase(),
rect.x + rect.width / 2,
rect.y + rect.height / 2,
{
size: 16,
color: isSelected ? ACCENT : "#888",
shadow: isSelected,
align: "center"
}
);
};
// ============================================================
// WINDOW_SKILLLIST — Style Persona 5 Clean
// ============================================================
const _WSL_init = Window_SkillList.prototype.initialize;
Window_SkillList.prototype.initialize = function(rect) {
_WSL_init.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
};
const _WSL_refresh = Window_SkillList.prototype.refresh;
Window_SkillList.prototype.refresh = function() {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) {
drawBg(ctx, this.contentsWidth(), this.contentsHeight(), {
barSide: 'left',
barSize: 5,
speedLines: false,
bgAlpha: 1.0
});
}
}
_WSL_refresh.call(this);
};
Window_SkillList.prototype.drawItem = function(index) {
const skill = this.itemAt(index);
if (!skill) return;
const rect = this.itemLineRect(index);
const ctx = getCtx(this.contents);
const pad = 6;
const skew = 8;
const isSelected = (index === this.index());
const actor = this._actor;
// Fond sombre
ctx.save();
ctx.fillStyle = "#000";
ctx.globalAlpha = 0.45;
ctx.fillRect(rect.x + pad, rect.y + pad,
rect.width - pad * 2, rect.height - pad * 2);
ctx.restore();
// Fond oblique stylé
drawSkewedRect(
ctx,
rect.x + pad,
rect.y + pad,
rect.width - pad * 2,
rect.height - pad * 2,
skew,
isSelected ? COLOR1 : ACCENT,
isSelected ? 0.25 : 0.12
);
// Contour si sélectionné
if (isSelected) {
ctx.save();
ctx.strokeStyle = PAINT;
ctx.globalAlpha = 0.9;
ctx.lineWidth = 2;
ctx.strokeRect(
rect.x + pad + 2,
rect.y + pad + 2,
rect.width - pad * 2 - 4,
rect.height - pad * 2 - 4
);
ctx.restore();
}
// Icône
this.drawIcon(
skill.iconIndex,
rect.x + pad + 10,
rect.y + (rect.height - ImageManager.iconHeight) / 2
);
// Nom de la compétence
p5Text(
ctx,
skill.name.toUpperCase(),
rect.x + pad + 50,
rect.y + rect.height / 2,
{
size: 16,
color: this.isEnabled(skill) ? ACCENT : "#666",
shadow: true
}
);
// Coût MP
const cost = this._actor.skillMpCost(skill);
const costStr = `${cost} MP`;
ctx.save();
ctx.font = 'bold 14px "Arial Black", Arial';
ctx.textBaseline = 'middle';
ctx.textAlign = 'right';
ctx.fillStyle = PAINT;
ctx.globalAlpha = this.isEnabled(skill) ? 1 : 0.4;
ctx.fillText(costStr, rect.x + rect.width - pad - 10, rect.y + rect.height / 2);
ctx.restore();
};
// ============================================================
// WINDOW_SKILLSTATUS — Portrait + barres stylées
// ============================================================
const _WSS_refresh = Window_SkillStatus.prototype.refresh;
Window_SkillStatus.prototype.refresh = function() {
if (!this.contents) return;
this.contents.clear();
const actor = this._actor;
if (!actor) return;
const ctx = getCtx(this.contents);
const W = this.contentsWidth();
const H = this.contentsHeight();
const pad = 10;
// Fond P5
drawBg(ctx, W, H, { barSide: 'top', barSize: 4, speedLines: false });
// Portrait
const faceSize = Math.min(H - 20, 96);
const fx = pad + 8, fy = pad;
ctx.save();
ctx.fillStyle = '#000'; ctx.globalAlpha = 0.55;
ctx.fillRect(fx - 4, fy - 4, faceSize + 8, faceSize + 8);
ctx.restore();
ctx.save();
ctx.strokeStyle = COLOR1; ctx.lineWidth = 2; ctx.globalAlpha = 0.9;
ctx.strokeRect(fx - 2, fy - 2, faceSize + 4, faceSize + 4);
ctx.restore();
this.drawFace(actor.faceName(), actor.faceIndex(), fx, fy, faceSize, faceSize);
// Infos
const tx = fx + faceSize + 16;
const ty = fy + 4;
const bW = W - tx - 16;
p5Text(ctx, actor.name().toUpperCase(), tx, ty + 2, { size: 20, color: ACCENT });
p5Text(ctx, actor.currentClass().name + ' – LV.' + actor.level, tx, ty + 24,
{ size: 12, color: '#999', shadow: false });
// HP
const hpR = actor.mhp > 0 ? actor.hp / actor.mhp : 0;
p5Text(ctx, `HP ${actor.hp}/${actor.mhp}`, tx, ty + 44, { size: 11, color: '#00E055', shadow: false });
drawStatBar(ctx, tx, ty + 56, bW, 8, hpR, '#00E055');
// MP
const mpR = actor.mmp > 0 ? actor.mp / actor.mmp : 0;
p5Text(ctx, `MP ${actor.mp}/${actor.mmp}`, tx, ty + 70, { size: 11, color: '#4488FF', shadow: false });
drawStatBar(ctx, tx, ty + 82, bW, 8, mpR, '#4488FF');
};
// Fonction utilitaire pour les barres
function drawBar(ctx, x, y, w, h, rate, color, back) {
ctx.save();
ctx.fillStyle = back;
ctx.globalAlpha = 0.6;
ctx.fillRect(x, y, w, h);
ctx.fillStyle = color;
ctx.globalAlpha = 1;
ctx.fillRect(x, y, w * Math.max(0, Math.min(1, rate)), h);
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
ctx.globalAlpha = 0.8;
ctx.strokeRect(x, y, w, h);
ctx.restore();
}
// ============================================================
// WINDOW_EQUIPCOMMAND — Style Persona 5 x SF6
// ============================================================
const _WEC_init = Window_EquipCommand.prototype.initialize;
Window_EquipCommand.prototype.initialize = function(rect) {
_WEC_init.call(this, rect);
this.opacity = 0; this.frameVisible = false;
};
Window_EquipCommand.prototype.select = function(index) {
const prev = this._index !== undefined ? this._index : -1;
Window_Command.prototype.select.call(this, index);
if (prev !== index && this.contents) {
if (prev >= 0 && prev < this.maxItems()) this.redrawItem(prev);
if (index >= 0 && index < this.maxItems()) this.redrawItem(index);
}
};
const _WEC_refresh = Window_EquipCommand.prototype.refresh;
Window_EquipCommand.prototype.refresh = function() {
if (this.contents) {
const ctx = getCtx(this.contents);
if (ctx) drawBg(ctx, this.contentsWidth(), this.contentsHeight(),
{ barSide: 'top', barSize: 4, speedLines: false });
}
_WEC_refresh.call(this);
};
Window_EquipCommand.prototype.drawItem = function(index) {
const rect = this.itemRect(index);
const ctx = getCtx(this.contents);
if (!ctx) return;
const name = this.commandName(index);
const isSelected = (index === this.index());
const pad = 5, skew = 10;
// Fond oblique
drawSkewedRect(ctx, rect.x + pad, rect.y + pad,
rect.width - pad * 2, rect.height - pad * 2, skew,
isSelected ? COLOR1 : ACCENT,
isSelected ? 0.35 : 0.08);
// Trait doré sous l'item sélectionné
if (isSelected) {
ctx.save();
ctx.fillStyle = PAINT; ctx.globalAlpha = 0.85;
ctx.fillRect(rect.x + pad + 8, rect.y + rect.height - 5, rect.width - pad * 2 - 16, 3);
ctx.restore();
}
p5Text(ctx, name.toUpperCase(),
rect.x + rect.width / 2, rect.y + rect.height / 2,
{ size: 15, color: isSelected ? ACCENT : '#888', shadow: isSelected, align: 'center' });
};
// ============================================================
// WINDOW_EQUIPSTATUS — Portrait + stats P5 style
// ============================================================
Window_EquipStatus.prototype.initialize = function(rect) {
Window_StatusBase.prototype.initialize.call(this, rect);
this.opacity = 0; this.frameVisible = false;
this._actor = null; this._tempActor = null;
};
Window_EquipStatus.prototype.refresh = function() {
if (!this.contents) return;
this.contents.clear();
const actor = this._actor;
const ctx = getCtx(this.contents);
if (!ctx) return;
const W = this.contentsWidth();
const H = this.contentsHeight();
// Fond P5
drawBg(ctx, W, H, { barSide: 'left', barSize: 5, speedLines: false });
if (!actor) return;
// Portrait (sans cadre noir, remonté au maximum)
const faceSize = Math.min(108, W - 15);
const fx = Math.floor((W - faceSize) / 3);
const fy = -24;
this.drawFace(actor.faceName(), actor.faceIndex(), fx, fy, faceSize, faceSize);
ctx.save();
ctx.strokeStyle = COLOR1; ctx.lineWidth = 2; ctx.globalAlpha = 0.9;
ctx.strokeRect(fx - 2, fy - 2, faceSize + 4, faceSize + 4);
ctx.restore();
// Nom + classe
p5Text(ctx, actor.name().toUpperCase(),
W / 2, fy + faceSize + 12, { size: 15, color: ACCENT, align: 'center' });
p5Text(ctx, actor.currentClass().name.toUpperCase(),
W / 2, fy + faceSize + 28, { size: 10, color: '#AAA', shadow: false, align: 'center' });
// Trait séparateur
ctx.save();
ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.5;
ctx.fillRect(10, fy + faceSize + 38, W - 20, 2);
ctx.restore();
// Stats avec comparaison tempActor
const statDefs = [
{ label: 'ATK', id: 2, color: '#FF6666' },
{ label: 'DEF', id: 3, color: '#66AAFF' },
{ label: 'M.ATK', id: 4, color: '#CC66FF' },
{ label: 'M.DEF', id: 5, color: '#66CCFF' },
{ label: 'AGI', id: 6, color: '#66FF99' },
{ label: 'LUK', id: 7, color: PAINT },
];
const statStartY = fy + faceSize + 44;
const rowH = Math.floor((H - statStartY - 8) / statDefs.length);
statDefs.forEach((st, i) => {
const sy = statStartY + i * rowH;
const curVal = actor.param(st.id);
const tempVal = this._tempActor ? this._tempActor.param(st.id) : null;
// Fond cellule
ctx.save();
ctx.fillStyle = st.color; ctx.globalAlpha = 0.07;
ctx.fillRect(10, sy + 2, W - 20, rowH - 4);
ctx.fillStyle = st.color; ctx.globalAlpha = 0.6;
ctx.fillRect(10, sy + 2, 3, rowH - 4);
ctx.restore();
p5Text(ctx, st.label, 18, sy + rowH / 2, { size: 10, color: st.color, shadow: false });
p5Text(ctx, String(curVal), W / 2 - 10, sy + rowH / 2, { size: 14, color: ACCENT, shadow: false, align: 'right' });
// Flèche + nouvelle valeur si équipement sélectionné
if (tempVal !== null && tempVal !== curVal) {
const arrowColor = tempVal > curVal ? '#00EE66' : '#FF4444';
const arrow = tempVal > curVal ? '▲' : '▼';
ctx.save();
ctx.font = 'bold 10px Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = arrowColor; ctx.globalAlpha = 1;
ctx.textAlign = 'left';
ctx.fillText(arrow + ' ' + String(tempVal), W / 2 + 2, sy + rowH / 2);
ctx.restore();
} else if (tempVal !== null) {
ctx.save();
ctx.font = 'bold 10px Arial'; ctx.textBaseline = 'middle';
ctx.fillStyle = '#555'; ctx.globalAlpha = 0.8;
ctx.textAlign = 'left';
ctx.fillText('—', W / 2 + 2, sy + rowH / 2);
ctx.restore();
}
});
};
Window_EquipStatus.prototype.setTempActor = function(tempActor) {
if (this._tempActor !== tempActor) {
this._tempActor = tempActor;
this.refresh();
}
};
// ============================================================
// SCENE_EQUIP — Layout P5 x SF6 — plein écran
// ============================================================
Scene_Equip.prototype.helpWindowRect = function() {
return new Rectangle(0, -200, 0, 0);
};
Scene_Equip.prototype.statusWindowRect = function() {
const ww = Math.floor(Graphics.boxWidth * 0.36);
const bannerH = Math.floor(Graphics.boxHeight * 0.22);
return new Rectangle(0, bannerH, ww, Graphics.boxHeight - bannerH);
};
Scene_Equip.prototype.commandWindowRect = function() {
const sx = Math.floor(Graphics.boxWidth * 0.36) + 4;
const ww = Graphics.boxWidth - sx;
const bannerH = Math.floor(Graphics.boxHeight * 0.22);
const cmdH = 140;
return new Rectangle(sx, bannerH + 4, ww, cmdH);
};
Scene_Equip.prototype.slotWindowRect = function() {
const sx = Math.floor(Graphics.boxWidth * 0.36) + 4;
const ww = Graphics.boxWidth - sx;
const bannerH = Math.floor(Graphics.boxHeight * 0.22);
const cmdH = 140;
const cy = bannerH + 4 + cmdH + 4;
const wh = Graphics.boxHeight - cy - 4;
return new Rectangle(sx, cy, ww, wh);
};
Scene_Equip.prototype.itemWindowRect = function() {
return this.slotWindowRect();
};
// Hauteur des onglets de commande
Window_EquipCommand.prototype.itemHeight = function() { return 120; };
// Padding réduit pour que les boutons de slot s'étendent au maximum à droite.
// Important : ne pas assigner prototype.padding, car RPG Maker déclenche le setter
// avant la création des children internes de Window au chargement du plugin.
Window_EquipSlot.prototype.itemPadding = function() { return 2; };
// Ne pas surcharger les méthodes internes de scroll : certains cores MZ/plugins
// les utilisent au lancement et cela peut bloquer le jeu.
// ── Bandeau décoratif PLEINE LARGEUR en haut ─────────────────
const _SceneEquip_create = Scene_Equip.prototype.create;
Scene_Equip.prototype.create = function() {
_SceneEquip_create.call(this);
const bannerH = Math.floor(Graphics.boxHeight * 0.30);
this._equipBanner = new Sprite(new Bitmap(Graphics.boxWidth, bannerH));
this._equipBanner.x = 0;
this._equipBanner.y = 0;
this.addChildAt(this._equipBanner, 1);
this._drawEquipBanner();
};
Scene_Equip.prototype._drawEquipBanner = function() {
const bmp = this._equipBanner.bitmap;
const ctx = bmp._canvas ? bmp._canvas.getContext('2d') : null;
if (!ctx) return;
const W = bmp.width, H = bmp.height;
// Fond pleine largeur — dégradé diagonal sombre
ctx.save();
const grad = ctx.createLinearGradient(0, 0, W, H);
grad.addColorStop(0, '#0D0008');
grad.addColorStop(0.4, '#180010');
grad.addColorStop(1, '#080004');
ctx.fillStyle = grad;
ctx.globalAlpha = 0.97;
ctx.fillRect(0, 0, W, H);
ctx.restore();
// Bande rouge tout en haut
ctx.save();
ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.fillRect(0, 0, W, 5);
ctx.restore();
// Lignes de vitesse diagonales sur toute la largeur
ctx.save();
ctx.strokeStyle = COLOR1; ctx.lineWidth = 1.2; ctx.globalAlpha = 0.06;
for (let i = -2; i < 22; i++) {
const lx = (W / 18) * i;
ctx.beginPath(); ctx.moveTo(lx + 80, 0); ctx.lineTo(lx - 60, H); ctx.stroke();
}
ctx.restore();
// Splashes déco — coin droit
drawPaintSplash(ctx, W * 0.82, H * 0.25, 100, COLOR1, 0.06);
drawPaintSplash(ctx, W * 0.92, H * 0.70, 60, PAINT, 0.05);
drawPaintSplash(ctx, W * 0.72, H * 0.80, 45, COLOR1, 0.04);
// Liseré vertical rouge gauche (continuation colonne status)
ctx.save();
const statusW = Math.floor(W * 0.30);
ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.fillRect(statusW, 0, 4, H);
ctx.restore();
// Grand titre "ÉQUIPEMENT"
const title = 'ÉQUIPEMENT';
const tx = statusW + 28;
const ty = H * 0.46;
ctx.save();
ctx.font = 'bold 72px "Arial Black", Arial, sans-serif';
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
ctx.fillStyle = COLOR1; ctx.globalAlpha = 0.45;
ctx.fillText(title, tx + 6, ty + 6);
ctx.fillStyle = PAINT; ctx.globalAlpha = 0.20;
ctx.fillText(title, tx + 2, ty + 2);
ctx.fillStyle = '#FFFFFF'; ctx.globalAlpha = 0.94;
ctx.fillText(title, tx, ty);
ctx.restore();
// Trait rouge épais sous le titre
const titleW = (() => {
ctx.save();
ctx.font = 'bold 72px "Arial Black", Arial, sans-serif';
const m = ctx.measureText(title).width;
ctx.restore();
return m;
})();
ctx.save();
ctx.fillStyle = COLOR1; ctx.globalAlpha = 1;
ctx.fillRect(tx, ty + 44, Math.min(titleW, W - tx - 30), 5);
ctx.restore();
ctx.save();
ctx.fillStyle = PAINT; ctx.globalAlpha = 0.55;
ctx.fillRect(tx, ty + 52, Math.min(titleW * 0.6, W - tx - 30), 2);
ctx.restore();
// Sous-titre "CHOISISSEZ UN ÉQUIPEMENT"
ctx.save();
ctx.font = 'bold 13px "Arial Black", Arial';
ctx.fillStyle = '#BBB'; ctx.globalAlpha = 0.65;
ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
ctx.fillText('CHOISISSEZ UN ÉQUIPEMENT', tx + 2, ty - 32);
ctx.restore();
};
// ============================================================
// RENAME SETTINGS → OPTIONS
// ============================================================
const _WMC_addOriginalCommands = Window_MenuCommand.prototype.addOriginalCommands;
Window_MenuCommand.prototype.addOriginalCommands = function() {
_WMC_addOriginalCommands.call(this);
// On remplace "Settings" par "Options"
const index = this._list.findIndex(cmd => cmd.symbol === "options");
if (index >= 0) {
this._list[index].name = "Options";
}
};
// ============================================================
// WINDOW_OPTIONS — Style Persona 5 Clean
// ============================================================
const _WO_init = Window_Options.prototype.initialize;
Window_Options.prototype.initialize = function(rect) {
_WO_init.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
};
const _WO_refresh = Window_Options.prototype.refresh;
Window_Options.prototype.refresh = function() {
if (this.contents) {
const ctx = getCtx(this.contents);
drawBg(ctx, this.contentsWidth(), this.contentsHeight(), {
barSide: 'left',
barSize: 5,
speedLines: false,
bgAlpha: 1.0
});
}
_WO_refresh.call(this);
};
Window_Options.prototype.drawItem = function(index) {
const rect = this.itemRect(index);
const ctx = getCtx(this.contents);
if (!ctx) return;
const isSelected = (index === this.index());
const pad = 8;
const skew = 10;
const W = rect.width;
const H = rect.height;
const ox = rect.x, oy = rect.y;
// Fond item
ctx.save();
ctx.fillStyle = '#000';
ctx.globalAlpha = 0.5;
ctx.fillRect(ox + pad, oy + 4, W - pad * 2, H - 8);
ctx.restore();
// Fond oblique sélection
drawSkewedRect(ctx, ox + pad, oy + 4, W - pad * 2, H - 8, skew,
isSelected ? COLOR1 : ACCENT, isSelected ? 0.22 : 0.08);
// Barre gauche
ctx.save();
ctx.fillStyle = isSelected ? PAINT : COLOR1;
ctx.globalAlpha = isSelected ? 1 : 0.4;
ctx.fillRect(ox + pad, oy + 4, 4, H - 8);
ctx.restore();
// Contour sélection
if (isSelected) {
ctx.save();
ctx.strokeStyle = PAINT; ctx.lineWidth = 1.5; ctx.globalAlpha = 0.8;
ctx.strokeRect(ox + pad + 2, oy + 6, W - pad * 2 - 4, H - 12);
ctx.restore();
}
// Séparateur bas (discret)
ctx.save();
ctx.strokeStyle = COLOR1; ctx.lineWidth = 0.5; ctx.globalAlpha = 0.12;
ctx.beginPath();
ctx.moveTo(ox + pad + 20, oy + H - 1);
ctx.lineTo(ox + W - pad, oy + H - 1);
ctx.stroke();
ctx.restore();
// Nom de l'option
const name = this.commandName(index);
const value = this.statusText(index);
p5Text(ctx, name.toUpperCase(), ox + pad + 14, oy + H / 2,
{ size: 15, color: isSelected ? ACCENT : '#CCC', shadow: isSelected });
// Badge valeur à droite
const valW = 80;
const valX = ox + W - pad - valW - 4;
const valY = oy + 6;
const valH = H - 12;
ctx.save();
ctx.fillStyle = isSelected ? PAINT : '#1A1A1A';
ctx.globalAlpha = isSelected ? 0.9 : 0.7;
const vsk = 6;
ctx.beginPath();
ctx.moveTo(valX + vsk, valY);
ctx.lineTo(valX + valW + vsk, valY);
ctx.lineTo(valX + valW - vsk, valY + valH);
ctx.lineTo(valX - vsk, valY + valH);
ctx.closePath(); ctx.fill();
ctx.restore();
p5Text(ctx, value, valX + valW / 2, oy + H / 2,
{ size: 14, color: isSelected ? '#000' : ACCENT,
shadow: false, align: 'center' });
};
// ============================================================
// FORCE SETTINGS → OPTIONS (pour menus custom)
// ============================================================
const _WMC_makeCommandList = Window_MenuCommand.prototype.makeCommandList;
Window_MenuCommand.prototype.makeCommandList = function() {
_WMC_makeCommandList.call(this);
for (const cmd of this._list) {
if (cmd.name === "Settings") {
cmd.name = "Options";
}
}
};
// ============================================================
// WINDOW_OPTIONS — Allonger les lignes
// ============================================================
Window_Options.prototype.itemRect = function(index) {
const rect = Window_Command.prototype.itemRect.call(this, index);
rect.width = this.contentsWidth() - 20; // <-- lignes plus longues
return rect;
};
// Redraw sur changement de sélection → surbrillance dynamique
const _WOpt_select_orig = Window_Options.prototype.select;
Window_Options.prototype.select = function(index) {
const prev = this._index !== undefined ? this._index : -1;
_WOpt_select_orig.call(this, index);
if (prev !== index) {
if (prev >= 0 && prev < this.maxItems()) this.redrawItem(prev);
if (index >= 0 && index < this.maxItems()) this.redrawItem(index);
}
};
// ============================================================================
// SCENE SAVE AAA — Menu Sauvegarde Premium
// ============================================================================
function Scene_SaveAAA() {
this.initialize(...arguments);
}
Scene_SaveAAA.prototype = Object.create(Scene_MenuBase.prototype);
Scene_SaveAAA.prototype.constructor = Scene_SaveAAA;
Scene_SaveAAA.prototype.initialize = function() {
Scene_MenuBase.prototype.initialize.call(this);
};
Scene_SaveAAA.prototype.create = function() {
Scene_MenuBase.prototype.create.call(this);
this.createHelpWindow();
this.createSaveList();
};
Scene_SaveAAA.prototype.start = function() {
Scene_MenuBase.prototype.start.call(this);
this._helpWindow.setText("Sélectionnez un fichier à sauvegarder.");
};
Scene_SaveAAA.prototype.createSaveList = function() {
const rect = this.saveListRect();
this._saveList = new Window_SaveListAAA(rect);
this._saveList.setHandler("ok", this.onSaveOk.bind(this));
this._saveList.setHandler("cancel", this.popScene.bind(this));
this.addWindow(this._saveList);
};
Scene_SaveAAA.prototype.saveListRect = function() {
const ww = Graphics.width - 40;
const wh = Graphics.height - this._helpWindow.height - 60;
const wx = 20;
const wy = this._helpWindow.height + 20;
return new Rectangle(wx, wy, ww, wh);
};
Scene_SaveAAA.prototype.onSaveOk = function() {
const id = this._saveList.index() + 1;
DataManager.saveGame(id)
.then(() => SoundManager.playSave())
.catch(() => SoundManager.playBuzzer());
this.popScene();
};
// ============================================================================
// SAUVEGARDE — Scène sans fenêtre de statut, liste plein écran
// ============================================================================
Scene_File.prototype.helpAreaHeight = function() { return 0; };
// helpWindow factice : existe (évite "height of null") mais invisible
const _SceneFile_createHelpWindow = Scene_File.prototype.createHelpWindow;
Scene_File.prototype.createHelpWindow = function() {
_SceneFile_createHelpWindow.call(this);
if (this._helpWindow) {
this._helpWindow.opacity = 0;
this._helpWindow.contentsOpacity = 0;
this._helpWindow.visible = false;
}
};
Scene_File.prototype.create = function() {
Scene_MenuBase.prototype.create.call(this);
this.createHelpWindow();
this.createListWindow();
};
Scene_File.prototype.createListWindow = function() {
const rect = this.listWindowRect();
this._listWindow = new Window_SavefileList(rect);
this._listWindow.setHandler("ok", this.onSavefileOk.bind(this));
this._listWindow.setHandler("cancel", this.popScene.bind(this));
this.addWindow(this._listWindow);
};
Scene_File.prototype.createStatusWindow = function() { /* désactivée */ };
// Liste plein écran
Scene_File.prototype.listWindowRect = function() {
return new Rectangle(0, 0, Graphics.boxWidth, Graphics.boxHeight);
};
// start() : gérer manuellement sans passer la helpWindow à la liste
Scene_File.prototype.start = function() {
Scene_MenuBase.prototype.start.call(this);
if (this._listWindow) {
this._listWindow.setMode(this.mode());
this._listWindow.select(this.firstSavefileId() - 1);
this._listWindow.activate();
this._listWindow.refresh();
}
};
// ── SAUVEGARDE ROBUSTE — patch DataManager.savefileInfo ─────────────────────
// Empêche "Save Error..." de s'afficher dans un slot après un échec
// En nettoyant l'info corrompue avant affichage
const _DM_savefileInfo_orig = DataManager.savefileInfo;
DataManager.savefileInfo = function(savefileId) {
const info = _DM_savefileInfo_orig.call(this, savefileId);
// Si l'info existe mais contient "Error" ou "error" dans le titre → on la nettoie
if (info && info.title &&
(info.title.toLowerCase().includes('error') ||
info.title.toLowerCase().includes('corrupt'))) {
return null; // Traité comme slot vide — propre
}
return info;
};
// Override onSaveSuccess : son + refresh + retour menu
const _SceneFile_onSaveSuccess_orig = Scene_File.prototype.onSaveSuccess;
Scene_File.prototype.onSaveSuccess = function() {
SoundManager.playSave();
if (this._listWindow) {
const idx = this._listWindow.index();
if (idx >= 0) this._listWindow.redrawItem(idx);
}
this.popScene();
};
// Override onSaveFailure : buzzer + slot propre + liste réactivée
Scene_File.prototype.onSaveFailure = function() {
SoundManager.playBuzzer();
if (this._listWindow) {
const idx = this._listWindow.index();
// Forcer la suppression de l'info corrompue du slot
if (idx >= 0) {
const fileId = this._listWindow.indexToSavefileId(idx);
// Supprimer les métadonnées erronées si elles existent
try {
if (DataManager._globalInfo && DataManager._globalInfo[fileId]) {
const inf = DataManager._globalInfo[fileId];
if (inf && inf.title && inf.title.toLowerCase().includes('error')) {
DataManager._globalInfo[fileId] = null;
}
}
} catch(e) {}
this._listWindow.redrawItem(idx);
}
this._listWindow.activate();
}
};
// Override onSavefileOk : s'assurer que la scène gère bien le save
Scene_File.prototype.onSavefileOk = function() {
const savefileId = this.savefileId();
if (this.mode() === 'save') {
$gameSystem.onBeforeSave();
DataManager.saveGame(savefileId)
.then(() => this.onSaveSuccess())
.catch(() => this.onSaveFailure());
} else {
// Mode load — comportement natif
DataManager.loadGame(savefileId)
.then(() => {
this.onLoadSuccess();
})
.catch(() => {
this.onLoadFailure();
});
}
};
// Neutraliser setHelpWindow sur la liste → plus de cascades vers la helpWindow
Window_SavefileList.prototype.setHelpWindow = function() { /* no-op */ };
// ============================================================================
// WINDOW_SAVEFILELIST — rendu magnifié style P5/SF6
// ============================================================================
const _SFL_initialize_orig = Window_SavefileList.prototype.initialize;
Window_SavefileList.prototype.initialize = function(rect) {
_SFL_initialize_orig.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
};
Window_SavefileList.prototype.maxItems = function() {
return DataManager.maxSavefiles();
};
// 1 seule colonne → liste verticale
Window_SavefileList.prototype.maxCols = function() {
return 1;
};
// Hauteur fixe généreuse pour chaque slot
Window_SavefileList.prototype.itemHeight = function() {
return 120;
};
// La liste scroll verticalement si > slots visibles
Window_SavefileList.prototype.numVisibleRows = function() {
return Math.floor(this.innerHeight / this.itemHeight());
};
// Fond général de la liste
const _SFL_refresh_orig = Window_SavefileList.prototype.refresh;
Window_SavefileList.prototype.refresh = function() {
if (this._sflRefreshing) return;
this._sflRefreshing = true;
try {
if (this.contents) {
// Fond global
const ctx = this.contents._canvas
? this.contents._canvas.getContext('2d')
: null;
if (ctx) {
const W = this.contentsWidth();
const H = this.contentsHeight();
ctx.clearRect(0, 0, W, H);
// Gradient noir/rouge sombre
const grad = ctx.createLinearGradient(0, 0, W, H);
grad.addColorStop(0, '#1A0008');
grad.addColorStop(0.5, '#0D0D0D');
grad.addColorStop(1, '#1A0008');
ctx.fillStyle = grad; ctx.globalAlpha = 0.97;
ctx.fillRect(0, 0, W, H);
// Trait rouge vertical à gauche
ctx.fillStyle = '#E8003A'; ctx.globalAlpha = 1;
ctx.fillRect(0, 0, 6, H);
// Traits horizontaux de séparation
const n = DataManager.maxSavefiles();
const ih = Math.floor(H / n);
ctx.strokeStyle = '#E8003A'; ctx.lineWidth = 1; ctx.globalAlpha = 0.25;
for (let i = 1; i < n; i++) {
ctx.beginPath();
ctx.moveTo(6, i * ih);
ctx.lineTo(W, i * ih);
ctx.stroke();
}
// Lignes de vitesse subtiles
ctx.strokeStyle = '#FFD700'; ctx.lineWidth = 0.5; ctx.globalAlpha = 0.04;
for (let i = 0; i < 12; i++) {
const x = Math.random() * W;
ctx.beginPath();
ctx.moveTo(x, 0); ctx.lineTo(x - 40, H);
ctx.stroke();
}
}
}
_SFL_refresh_orig.call(this);
} finally {
this._sflRefreshing = false;
}
};
Window_SavefileList.prototype.drawItem = function(index) {
const fileId = this.indexToSavefileId(index);
const info = DataManager.savefileInfo(fileId);
const rect = this.itemRect(index);
const ctx = this.contents._canvas
? this.contents._canvas.getContext('2d')
: null;
if (!ctx) { Window_Selectable.prototype.drawItem.call(this, index); return; }
const W = rect.width;
const H = rect.height;
const ox = rect.x;
const oy = rect.y;
const isSelected = (index === this.index());
const valid = !!info;
const pad = 10;
// ── FOND ITEM ──────────────────────────────────────────────
ctx.save();
if (isSelected) {
const selGrad = ctx.createLinearGradient(ox, oy, ox + W, oy);
selGrad.addColorStop(0, '#E8003A');
selGrad.addColorStop(0.5, '#A0001F');
selGrad.addColorStop(1, '#180008');
ctx.fillStyle = selGrad;
ctx.globalAlpha = 0.88;
ctx.fillRect(ox + 4, oy + 3, W - 8, H - 6);
} else {
ctx.fillStyle = valid ? '#FFFFFF' : '#0A0A0A';
ctx.globalAlpha = valid ? 0.04 : 0.03;
ctx.fillRect(ox + 4, oy + 3, W - 8, H - 6);
}
ctx.restore();
// ── BARRE VERTICALE GAUCHE ─────────────────────────────────
ctx.save();
ctx.fillStyle = isSelected ? '#FFD700' : (valid ? '#E8003A' : '#2A2A2A');
ctx.globalAlpha = 1;
ctx.fillRect(ox + 4, oy + 3, 5, H - 6);
ctx.restore();
// ── BADGE NUMÉRO (oblique) ─────────────────────────────────
const badgeW = 62;
const badgeH = H - 20;
const badgeX = ox + 14;
const badgeY = oy + 10;
const sk = 8;
ctx.save();
ctx.fillStyle = isSelected ? '#FFD700' : '#E8003A';
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(badgeX + sk, badgeY);
ctx.lineTo(badgeX + badgeW + sk, badgeY);
ctx.lineTo(badgeX + badgeW - sk, badgeY + badgeH);
ctx.lineTo(badgeX - sk, badgeY + badgeH);
ctx.closePath(); ctx.fill();
// "SAVE"
ctx.font = 'bold 9px "Arial Black", Arial';
ctx.fillStyle = isSelected ? '#1A0008' : '#FFD700';
ctx.globalAlpha = 1;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('SAVE', badgeX + badgeW / 2, badgeY + 6);
// Numéro
ctx.font = 'bold 34px "Arial Black", Arial';
ctx.fillStyle = isSelected ? '#1A0008' : '#FFFFFF';
ctx.textBaseline = 'middle';
ctx.fillText(String(fileId), badgeX + badgeW / 2, badgeY + badgeH / 2 + 8);
ctx.restore();
// ── SPRITES DES PERSONNAGES (via drawCharacter natif) ──────
const spriteStartX = badgeX + badgeW + 20;
if (valid && info.characters && info.characters.length > 0) {
const maxChars = Math.min(info.characters.length, 4);
const spriteW = 50;
const spriteY = oy + H / 2 + 16; // ancrage pieds au milieu-bas
// Booster la visibilité des sprites : fond léger + opacité haute
ctx.save();
ctx.globalAlpha = 1.0; // pleine opacité pour les sprites
ctx.restore();
for (let i = 0; i < maxChars; i++) {
const ch = info.characters[i];
if (ch && ch[0]) {
// Sauver l'état canvas, forcer opacité max, dessiner, restaurer
const tmpAlpha = this.contents.paintOpacity;
this.contents.paintOpacity = 255; // opacité max (255 = 100%)
this.drawCharacter(ch[0], ch[1] || 0, spriteStartX + i * spriteW + 24, spriteY);
this.contents.paintOpacity = tmpAlpha;
}
}
}
// ── ZONE TEXTE (titre + détails) ──────────────────────────
const charZoneW = valid && info && info.characters ? Math.min(info.characters.length, 4) * 50 + 10 : 0;
const textX = spriteStartX + charZoneW + (charZoneW > 0 ? 12 : 0);
if (valid) {
// Titre
const rawTitle = info.title || ('Partie ' + fileId);
const title = rawTitle.toUpperCase();
ctx.save();
ctx.font = 'bold 22px "Arial Black", Arial';
ctx.fillStyle = isSelected ? '#FFD700' : '#FFFFFF';
ctx.globalAlpha = 1;
ctx.textAlign = 'left';
ctx.textBaseline= 'top';
ctx.save();
ctx.rect(textX, oy, W - textX - 170, H);
ctx.clip();
ctx.fillText(title, textX, oy + 14);
ctx.restore();
ctx.restore();
// (noms de fichiers supprimés — on garde uniquement les sprites)
// Temps de jeu
if (info.playtime) {
ctx.save();
ctx.font = 'bold 13px "Arial Black", Arial';
ctx.fillStyle = isSelected ? '#FFD700' : '#999';
ctx.globalAlpha = 0.85;
ctx.textAlign = 'left';
ctx.textBaseline= 'middle';
ctx.fillText('⏱ ' + info.playtime, textX, oy + H / 2 + 8);
ctx.restore();
}
// ── BADGE DATE à droite ────────────────────────────────
if (info.timestamp) {
const d = new Date(info.timestamp);
const ds = d.toLocaleDateString('fr-FR', { day:'2-digit', month:'2-digit', year:'numeric' });
const ts = d.toLocaleTimeString('fr-FR', { hour:'2-digit', minute:'2-digit' });
const dateW = 148;
const dateH = H - 28;
const dateX = ox + W - dateW - pad;
const dateY = oy + 14;
ctx.save();
// Fond badge
ctx.fillStyle = isSelected ? 'rgba(255,215,0,0.12)' : 'rgba(20,5,5,0.75)';
ctx.globalAlpha = 1;
ctx.fillRect(dateX, dateY, dateW, dateH);
// Ligne gauche
ctx.fillStyle = isSelected ? '#FFD700' : '#E8003A';
ctx.fillRect(dateX, dateY, 3, dateH);
// Date
ctx.font = 'bold 15px "Arial Black", Arial';
ctx.fillStyle = '#FFD700';
ctx.globalAlpha = 1;
ctx.textAlign = 'center';
ctx.textBaseline= 'middle';
ctx.fillText(ds, dateX + dateW / 2, dateY + dateH / 2 - 11);
// Heure
ctx.font = '13px Arial';
ctx.fillStyle = isSelected ? '#FFF' : '#BBB';
ctx.globalAlpha = 0.9;
ctx.fillText(ts, dateX + dateW / 2, dateY + dateH / 2 + 11);
ctx.restore();
}
} else {
// VIDE
ctx.save();
ctx.font = 'bold 16px "Arial Black", Arial';
ctx.fillStyle = '#2A2A2A';
ctx.globalAlpha = 1;
ctx.textAlign = 'left';
ctx.textBaseline= 'middle';
ctx.fillText('— VIDE —', spriteStartX, oy + H / 2);
ctx.restore();
}
// ── SÉPARATEUR ────────────────────────────────────────────
ctx.save();
ctx.strokeStyle = isSelected ? '#FFD700' : '#E8003A';
ctx.lineWidth = 0.5;
ctx.globalAlpha = isSelected ? 0.4 : 0.12;
ctx.beginPath();
ctx.moveTo(ox + 4, oy + H - 1);
ctx.lineTo(ox + W - 4, oy + H - 1);
ctx.stroke();
ctx.restore();
// ── FLÈCHE si sélectionné ─────────────────────────────────
if (isSelected) {
ctx.save();
ctx.fillStyle = '#FFD700';
ctx.globalAlpha = 1;
const ax = ox + W - pad - 18;
const ay = oy + H / 2;
ctx.beginPath();
ctx.moveTo(ax, ay - 9);
ctx.lineTo(ax + 14, ay);
ctx.lineTo(ax, ay + 9);
ctx.closePath(); ctx.fill();
ctx.restore();
}
};
// Forcer le redraw quand le curseur change de case
const _SFL_select_orig = Window_SavefileList.prototype.select;
Window_SavefileList.prototype.select = function(index) {
const prev = this._index !== undefined ? this._index : -1;
_SFL_select_orig.call(this, index);
if (prev !== index) {
if (prev >= 0 && prev < this.maxItems()) this.redrawItem(prev);
if (index >= 0 && index < this.maxItems()) this.redrawItem(index);
}
};
// ============================================================================
// SCENE_GAME_END — sous-menu "Quitter" stylé Persona 5 x SF6
// ============================================================================
Scene_GameEnd.prototype.create = function() {
Scene_MenuBase.prototype.create.call(this);
this.createCommandWindow();
};
Scene_GameEnd.prototype.createBackground = function() {
Scene_MenuBase.prototype.createBackground.call(this);
};
Scene_GameEnd.prototype.createCommandWindow = function() {
const rect = this.commandWindowRect();
this._commandWindow = new Window_GameEndAAA(rect);
this._commandWindow.setHandler('toTitle', this.commandToTitle.bind(this));
this._commandWindow.setHandler('quit', this.commandQuit.bind(this));
this._commandWindow.setHandler('cancel', this.popScene.bind(this));
this.addWindow(this._commandWindow);
};
Scene_GameEnd.prototype.commandQuit = function() {
// Quitter entièrement le jeu (NW.js / Electron)
SceneManager.exit();
};
Scene_GameEnd.prototype.commandWindowRect = function() {
// Centré à l'écran — hauteur augmentée pour 3 boutons
const ww = 460;
const wh = 330;
const wx = (Graphics.boxWidth - ww) / 2;
const wy = (Graphics.boxHeight - wh) / 2;
return new Rectangle(wx, wy, ww, wh);
};
// ── WINDOW_GAMEEND CUSTOM ────────────────────────────────────────────────────
function Window_GameEndAAA() { this.initialize(...arguments); }
Window_GameEndAAA.prototype = Object.create(Window_Command.prototype);
Window_GameEndAAA.prototype.constructor = Window_GameEndAAA;
Window_GameEndAAA.prototype.initialize = function(rect) {
Window_Command.prototype.initialize.call(this, rect);
this.opacity = 0;
this.frameVisible = false;
this.refresh();
this.activate();
this.select(0);
};
Window_GameEndAAA.prototype.makeCommandList = function() {
this.addCommand('RETOUR AU TITRE', 'toTitle');
this.addCommand('RETOUR AU BUREAU', 'quit');
this.addCommand('ANNULER', 'cancel');
};
Window_GameEndAAA.prototype.maxCols = function() { return 1; };
Window_GameEndAAA.prototype.itemHeight = function() { return 80; };
Window_GameEndAAA.prototype.select = function(index) {
const prev = this._index !== undefined ? this._index : -1;
Window_Command.prototype.select.call(this, index);
if (prev !== index) {
if (this.contents) {
if (prev >= 0 && prev < this.maxItems()) this.redrawItem(prev);
if (index >= 0 && index < this.maxItems()) this.redrawItem(index);
}
}
};
Window_GameEndAAA.prototype.drawItem = function(index) {
const rect = this.itemRect(index);
const ctx = this.contents._canvas
? this.contents._canvas.getContext('2d')
: null;
if (!ctx) { Window_Command.prototype.drawItem.call(this, index); return; }
const W = rect.width;
const H = rect.height;
const ox = rect.x;
const oy = rect.y;
const isSelected = (index === this.index());
const isToTitle = (index === 0);
const isQuit = (index === 1);
const isCancel = (index === 2);
const sk = 14;
// Couleur accent selon le bouton
const accentColor = isToTitle ? '#E8003A' : isQuit ? '#FF6600' : '#FFD700';
// Fond du bouton
ctx.save();
if (isSelected) {
const grad = ctx.createLinearGradient(ox, oy, ox + W, oy);
grad.addColorStop(0, isToTitle ? '#E8003A' : isQuit ? '#FF4400' : '#1A1A2E');
grad.addColorStop(0.6, isToTitle ? '#A0001F' : isQuit ? '#AA2200' : '#16213E');
grad.addColorStop(1, '#0D0010');
ctx.fillStyle = grad;
ctx.globalAlpha = 0.95;
} else {
ctx.fillStyle = '#0A000D';
ctx.globalAlpha = 0.75;
}
// Forme oblique SF6
ctx.beginPath();
ctx.moveTo(ox + sk, oy + 6);
ctx.lineTo(ox + W - sk * 2, oy + 6);
ctx.lineTo(ox + W - sk, oy + H - 6);
ctx.lineTo(ox + sk * 2, oy + H - 6);
ctx.closePath();
ctx.fill();
ctx.restore();
// Trait gauche coloré
ctx.save();
ctx.fillStyle = isSelected ? accentColor : '#333';
ctx.globalAlpha = 1;
ctx.fillRect(ox + sk + 2, oy + 6, 4, H - 12);
ctx.restore();
// Texte principal
const label = this.commandName(index);
ctx.save();
ctx.font = 'bold 20px "Arial Black", Arial';
ctx.fillStyle = isSelected ? '#FFFFFF' : '#555';
ctx.globalAlpha = isSelected ? 1 : 0.5;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
if (isSelected) {
ctx.shadowColor = accentColor;
ctx.shadowBlur = 12;
}
ctx.fillText(label, ox + W / 2 + 6, oy + H / 2);
ctx.restore();
// Petite icône gauche
const icon = isToTitle ? '⏎' : isQuit ? '✕' : '↩';
ctx.save();
ctx.font = 'bold 16px Arial';
ctx.fillStyle = isSelected ? accentColor : '#333';
ctx.globalAlpha = isSelected ? 1 : 0.4;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(icon, ox + sk + 16, oy + H / 2);
ctx.restore();
};
Window_GameEndAAA.prototype.drawBackground = function() {
const ctx = this.contents._canvas
? this.contents._canvas.getContext('2d')
: null;
if (!ctx) return;
const W = this.contentsWidth();
const H = this.contentsHeight();
// Fond général avec fond foncé + lignes diagonales
drawBg(ctx, W, H, { barSide: 'left', barSize: 4, speedLines: false,
splashes: [{ x: W * 0.5, y: H * 0.5, size: 140, color: '#E8003A', alpha: 0.05 }] });
// Titre "QUITTER ?" centré en haut
ctx.save();
ctx.font = 'bold 28px "Arial Black", Arial';
ctx.fillStyle = '#FFFFFF';
ctx.globalAlpha = 0.9;
ctx.shadowColor = '#E8003A';
ctx.shadowBlur = 16;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText('QUITTER ?', W / 2 + 6, 12);
ctx.restore();
// Trait rouge sous le titre
ctx.save();
ctx.fillStyle = '#E8003A';
ctx.globalAlpha = 1;
ctx.fillRect(W * 0.2, 46, W * 0.6, 3);
ctx.restore();
};
Window_GameEndAAA.prototype.refresh = function() {
Window_Command.prototype.refresh.call(this);
if (this.contents) this.drawBackground();
this.drawAllItems();
};
console.log('[UniqueMenu_MZ] Sauvegarde premium — Persona5 x SF6');
})();
To embed this project on your website, copy the following code and paste it into your website's HTML: