Плагин на изменения боя. Боевая сетка (Внимание сырой)

Больше
2 нед. 5 дн. назад - 2 нед. 1 день назад #132782 от Ryterator
Плагин создал мне deepseek Если вы против нейросетей как инструмента помощи в творчестве, просто не используйте этот плагин .
Пример того, что он делает.
Для установки смените окончание\расшерение файла с txt на Js.
Я не считаю нейросеть вредной в творчестве, но я сторонник общедоступности того, что было ею сделано, так как она учится и работает на открытом контенте сообщества.
Плагин предоставляется «как есть». Можете проверить его самостоятельно перед установкой. Код представлен ниже.
!Инструкция по устранению неполадок!
Следуйте по пунктам:
1) Проверьте настройки плагина, примечания к навыкам и т.д. Всё, что вы делали.
2) Если выше не помогло, откройте VS code потом Deepseek киньте ему файл и скажите свою проблему. Патч или другие решения в файл просите скинуть целым файлом он даст вам блок кода для вставки от начала и до конца.
3) Если всё остальное не помогло мой Discord: 1212_3949 Отвечаю где то в течение недели пишите сразу с причины обращения ЛС открыт.

UPD Визуал перенесён в отдельный плагин, создан плагин для подкреплений в драку. код всего ниже 04.10.2026 утром по мск вроде бы
UPD Добавил плагин для переодевания в бою, обновил проблемы кторые нашёл

Код:
ВНИМАНИЕ: Спойлер!
Code:
/*: * @target MZ * @plugindesc v7.5 Formation Grid: сетка + механики (без спрайтов/анимаций врагов) * @author Custom * * @help * ============================================================ *  Battle Formation Grid v7.5 * ============================================================ *  Сетка: бойцы внутри линии стоят СТОЛБИКОМ СНИЗУ ВВЕРХ, *  линии идут СЛЕВА НАПРАВО. Линия 0 — фронт. * * --- ТЕГИ СКИЛЛОВ / ПРЕДМЕТОВ --- *  <ranged> <melee> *  <pierceLines:N>  <aoeLine:N>  <aoeColumn:N>  <aoeCross:N> *  <coverBack>  (state) * ============================================================ * * @command openFormation * @text Открыть построение * * @param attackIsMelee * @text Базовая «Атака» = ближняя * @type boolean * @default true * * @param partySize * @text Party: бойцов в линии * @type number * @min 1 * @max 6 * @default 3 * * @param partyLines * @text Party: линий * @type number * @min 1 * @max 6 * @default 3 * * @param partyOriginX * @text Party: Origin X * @type number * @default 540 * * @param partyOriginY * @text Party: Origin Y * @type number * @default 540 * * @param partyColSpacing * @text Party: шаг бойцов в линии * @type number * @default 80 * * @param partyRowSpacing * @text Party: шаг между линиями * @type number * @default 100 * * @param partyMirror * @text Party: Mirror * @type boolean * @default false * * @param partyFaceLeft * @text Party: смотрят влево * @type boolean * @default true * * @param enemySize * @text Enemy: бойцов в линии * @type number * @min 1 * @max 6 * @default 3 * * @param enemyLines * @text Enemy: линий * @type number * @min 1 * @max 6 * @default 3 * * @param enemyOriginX * @text Enemy: Origin X * @type number * @default 260 * * @param enemyOriginY * @text Enemy: Origin Y * @type number * @default 540 * * @param enemyColSpacing * @text Enemy: шаг бойцов в линии * @type number * @default 80 * * @param enemyRowSpacing * @text Enemy: шаг между линиями * @type number * @default 100 * * @param enemyMirror * @text Enemy: Mirror * @type boolean * @default true */ (() => {     "use strict";     const pluginName = "BattleFormationGrid";     const raw = PluginManager.parameters(pluginName);     const clamp = (v, a, b) => isNaN(v) ? a : Math.max(a, Math.min(b, v));     const P = {         attackIsMelee:  String(raw.attackIsMelee) !== "false",         partySize:      clamp(Number(raw.partySize      || 3), 1, 6),         partyLines:      clamp(Number(raw.partyLines      || 3), 1, 6),         partyOriginX:    Number(raw.partyOriginX    || 540),         partyOriginY:    Number(raw.partyOriginY    || 540),         partyColSpacing: Number(raw.partyColSpacing || 80),         partyRowSpacing: Number(raw.partyRowSpacing || 100),         partyMirror:    String(raw.partyMirror)    === "true",         partyFaceLeft:  String(raw.partyFaceLeft)  !== "false",         enemySize:      clamp(Number(raw.enemySize      || 3), 1, 6),         enemyLines:      clamp(Number(raw.enemyLines      || 3), 1, 6),         enemyOriginX:    Number(raw.enemyOriginX    || 260),         enemyOriginY:    Number(raw.enemyOriginY    || 540),         enemyColSpacing: Number(raw.enemyColSpacing || 80),         enemyRowSpacing: Number(raw.enemyRowSpacing || 100),         enemyMirror:    String(raw.enemyMirror)    !== "false"     };     // ============================================================     //  UI СЦЕНЫ ПОСТРОЕНИЯ     // ============================================================     const LAYOUT = {         grid: {             originX: 560, originY: 500,             stepX: 100, stepY: 90,             cellW: 100, cellH: 90,             innerPadding: 2         },         frontLabel: { x: 460, y: 260, charW: 30, charH: 26, fontSize: 22 },         lineLabel:  { offsetY: 36, w: 140, h: 20, fontSize: 16 },         infoLabel:  {             offsetY: 60, w: 180, h: 46,             nameFontSize: 17, hpFontSize: 12,             hpBarH: 10, hpBarMargin: 10, hpBarBottomGap: 10         },         helpWindow: { x: 40, y: 600, w: 1200, h: 70 },         linePanel:  { x: 40, y: 50, w: 220, h: 450, rowH: 38 },         cursor:    { extra: 8, thickness: 3 }     };     // ============================================================     //  УТИЛИТЫ     // ============================================================     function actorSlot(actor) {         if (!actor) return -1;         const map = $gameParty._formationSlots;         if (map) {             const key = String(actor.actorId());             if (Object.prototype.hasOwnProperty.call(map, key)) return Number(map[key]);         }         return $gameParty.allMembers().indexOf(actor);     }     function battlerSlot(b) {         if (!b) return -1;         if (b.isActor && b.isActor()) return actorSlot(b);         return b.index();     }     function gridPos(index, size, lines, ox, oy, cs, rs, mirror) {         const member = index % size;         const line  = Math.floor(index / size);         const dir    = mirror ? -1 : 1;         return {             x: Math.round(ox + line * rs * dir),             y: Math.round(oy - member * cs)         };     }     function getLineOfBattler(b) {         if (!b) return -1;         const s = battlerSlot(b);         if (s < 0) return -1;         const size = b.isActor() ? P.partySize : P.enemySize;         return Math.floor(s / size);     }     function getFrontLivingLineOfUnit(unit) {         if (!unit) return -1;         let min = Infinity;         for (const m of unit.aliveMembers()) {             const l = getLineOfBattler(m);             if (l >= 0 && l < min) min = l;         }         return min === Infinity ? -1 : min;     }     function unitLayout(unit) {         const isPartyUnit = !!unit._actors;         return {             size:  isPartyUnit ? P.partySize  : P.enemySize,             lines: isPartyUnit ? P.partyLines : P.enemyLines         };     }     function cellsOfUnit(unit) {         const { size } = unitLayout(unit);         const cells = [];         for (const m of unit.aliveMembers()) {             const s = battlerSlot(m);             if (s < 0) continue;             cells.push({                 member: m,                 memberIdx: s % size,                 line: Math.floor(s / size)             });         }         return cells;     }     function itemNote(action) { return (action.item() && action.item().note) || ""; }     function actionIsMelee(action) {         const note = itemNote(action);         if (/<ranged>/i.test(note)) return false;         if (/<melee>/i.test(note))  return true;         return P.attackIsMelee && action.isAttack();     }     function parsePierce(action) {         const m = /<pierceLines:(\d+)>/i.exec(itemNote(action));         return m ? clamp(Number(m[1]), 1, 6) : 1;     }     function parseAoeShapes(action) {         const note = itemNote(action);         const shapes = [];         const grab = (re, key) => {             const m = re.exec(note);             if (!m) return;             const n = m[1] ? clamp(Number(m[1]), 1, 36) : 36;             shapes.push({ key, n });         };         grab(/<aoeLine:(\d+)>/i,  "line");         grab(/<aoeColumn:(\d+)>/i, "column");         grab(/<aoeCross:(\d+)>/i,  "cross");         if (/<aoeLine>(?!\d)/i.test(note)  && !/<aoeLine:\d+>/i.test(note))             shapes.push({ key: "line", n: 36 });         if (/<aoeColumn>(?!\d)/i.test(note) && !/<aoeColumn:\d+>/i.test(note))             shapes.push({ key: "column", n: 36 });         if (/<aoeCross>(?!\d)/i.test(note)  && !/<aoeCross:\d+>/i.test(note))             shapes.push({ key: "cross", n: 36 });         return shapes;     }     // ============================================================     //  ПОЗИЦИИ СПРАЙТОВ В БОЮ     // ============================================================     Sprite_Actor.prototype.setActorHome = function(index) {         const battler = this._actor || this._battler;         const slot = (battler && battler.isActor && battler.isActor())             ? actorSlot(battler) : index;         const s = slot >= 0 ? slot : index;         const p = gridPos(             s,             P.partySize, P.partyLines,             P.partyOriginX, P.partyOriginY,             P.partyColSpacing, P.partyRowSpacing,             P.partyMirror         );         this.setHome(p.x, p.y);     };     Game_Enemy.prototype.screenX = function() {         return gridPos(this.index(), P.enemySize, P.enemyLines,             P.enemyOriginX, P.enemyOriginY, P.enemyColSpacing, P.enemyRowSpacing,             P.enemyMirror).x;     };     Game_Enemy.prototype.screenY = function() {         return gridPos(this.index(), P.enemySize, P.enemyLines,             P.enemyOriginX, P.enemyOriginY, P.enemyColSpacing, P.enemyRowSpacing,             P.enemyMirror).y;     };     Game_Actor.prototype.screenX = function() {         const s = actorSlot(this);         return gridPos(s >= 0 ? s : 0, P.partySize, P.partyLines,             P.partyOriginX, P.partyOriginY, P.partyColSpacing, P.partyRowSpacing,             P.partyMirror).x;     };     Game_Actor.prototype.screenY = function() {         const s = actorSlot(this);         return gridPos(s >= 0 ? s : 0, P.partySize, P.partyLines,             P.partyOriginX, P.partyOriginY, P.partyColSpacing, P.partyRowSpacing,             P.partyMirror).y;     };     // ============================================================     //  ФИЛЬТР ПО ПЕРЕДНЕЙ ЛИНИИ     // ============================================================     // Может ли атакующий вообще достать кого-то ближним боем     // (должен стоять в своей передней живой линии).     function meleeAttackerCanReach(action) {         if (!action) return true;         const subject = action.subject();         if (!subject) return true;         const ownFront = getFrontLivingLineOfUnit(subject.friendsUnit());         if (ownFront < 0) return true;         return getLineOfBattler(subject) === ownFront;     }     // null — достижимо; иначе текст причины.     function meleeBlockReason(action, target) {         if (!action || !target) return null;         if (!actionIsMelee(action)) return null;         if (parsePierce(action) > 1) return null;         if (!action.isForOpponent()) return null;         if (!meleeAttackerCanReach(action))             return "Атака ближнего боя невозможна из задней линии.";         const front = getFrontLivingLineOfUnit(action.opponentsUnit());         if (front < 0) return null;         if (getLineOfBattler(target) !== front)             return "Слишком далеко! Цель за передней линией.";         return null;     }     function targetIsReachable(action, target) {         return meleeBlockReason(action, target) === null;     }     function filterByFrontLine(action, targets) {         if (!targets || !targets.length) return targets;         if (!actionIsMelee(action)) return targets;         if (parsePierce(action) > 1) return targets;         const subject = action.subject();         const isActorSubject = subject && subject.isActor && subject.isActor();         // Атакующий ближним боем должен сам быть в своей передней линии.         // Игрока здесь не режем — ему покажем сообщение на подтверждении.         // Врагов (ИИ) отсекаем сразу, чтобы они не выбирали такую атаку.         if (subject && !meleeAttackerCanReach(action) && !isActorSubject) {             return [];         }         // Игрок сам решает, куда бить: цели не фильтруем,         // а на подтверждении покажем «Слишком далеко!».         if (isActorSubject && action.isForOpponent()) {             return targets;         }         const unit = action.isForOpponent()             ? action.opponentsUnit() : action.friendsUnit();         const front = getFrontLivingLineOfUnit(unit);         if (front < 0) return targets;         const filtered = targets.filter(t => getLineOfBattler(t) === front);         return filtered.length ? filtered : targets;     }     const _targetsForOpponents = Game_Action.prototype.targetsForOpponents;     Game_Action.prototype.targetsForOpponents = function() {         return filterByFrontLine(this, _targetsForOpponents.call(this));     };     const _targetsForFriends = Game_Action.prototype.targetsForFriends;     Game_Action.prototype.targetsForFriends = function() {         return filterByFrontLine(this, _targetsForFriends.call(this));     };     // ============================================================     //  AoE / PIERCE     // ============================================================     function expandAoe(action, targets) {         if (!targets.length) return targets;         const note = itemNote(action);         const pierce = parsePierce(action);         const shapes = parseAoeShapes(action);         const hasPierce = pierce > 1 && /<pierceLines:\d+>/i.test(note);         if (!hasPierce && !shapes.length) return targets;         const unit = action.isForOpponent()             ? action.opponentsUnit() : action.friendsUnit();         const cells = cellsOfUnit(unit);         if (!cells.length) return targets;         const result = new Set(targets);         if (hasPierce) {             const lines = [...new Set(cells.map(c => c.line))].sort((a, b) => a - b);             const activeLines = lines.slice(0, pierce);             for (const c of cells) if (activeLines.includes(c.line)) result.add(c.member);         }         for (const t of targets) {             const tcell = cells.find(c => c.member === t);             if (!tcell) continue;             for (const shape of shapes) {                 if (shape.key === "line") {                     const group = cells.filter(c => c.line === tcell.line);                     group.sort((a, b) =>                         Math.abs(a.memberIdx - tcell.memberIdx) -                         Math.abs(b.memberIdx - tcell.memberIdx));                     group.slice(0, shape.n).forEach(c => result.add(c.member));                 } else if (shape.key === "column") {                     const group = cells.filter(c => c.memberIdx === tcell.memberIdx);                     group.sort((a, b) => a.line - b.line);                     group.slice(0, shape.n).forEach(c => result.add(c.member));                 } else if (shape.key === "cross") {                     const cross = cells.filter(c =>                         c.line === tcell.line || c.memberIdx === tcell.memberIdx);                     cross.sort((a, b) => {                         const da = Math.abs(a.memberIdx - tcell.memberIdx) +                                   Math.abs(a.line - tcell.line);                         const db = Math.abs(b.memberIdx - tcell.memberIdx) +                                   Math.abs(b.line - tcell.line);                         return da - db;                     });                     cross.slice(0, shape.n).forEach(c => result.add(c.member));                 }             }         }         return [...result];     }     const _makeTargets = Game_Action.prototype.makeTargets;     Game_Action.prototype.makeTargets = function() {         let targets = _makeTargets.call(this);         if (this._forcing || !this.subject()) return targets;         if (!targets.length) return targets;         return expandAoe(this, targets);     };     // ============================================================     //  ПРИКРЫТИЕ ТЫЛА (все эффекты действия)     // ============================================================     function findCoverGuard(target, action) {         if (!target || !target.isActor()) return null;         if (getLineOfBattler(target) <= 0) return null;         // Перехватываем только враждебные действия по цели.         // Дружеские баффы/хилки на тыл проходят как есть.         if (action && action.isForOpponent && !action.isForOpponent()) return null;         for (const m of $gameParty.allMembers()) {             if (m === target) continue;             if (m.hp <= 0) continue;             if (getLineOfBattler(m) !== 0) continue;             if (m.states().some(s => s && /<coverBack>/i.test(s.note || ""))) return m;         }         return null;     }     // Хук на штатный механизм замещения. Он вызывается ДО apply()     // и предназначен ровно для подмены цели. Сработает для любых     // эффектов: HP/MP-урон, состояния, баффы, common events и т.д.     const _BM_applySubstitute = BattleManager.applySubstitute;     BattleManager.applySubstitute = function(target) {         // Сначала даём отработать встроенному <Substitute>-трейту.         target = _BM_applySubstitute.call(this, target);         // Затем — наш state-based <coverBack>.         const guard = findCoverGuard(target, this._action);         if (guard && guard.hp > 0 && guard !== target) {             if (this._logWindow && this._logWindow.displaySubstitute) {                 this._logWindow.displaySubstitute(guard, target);             }             return guard;         }         return target;     };         // ============================================================     //  СПРАЙТ АКТЁРА (сцена построения)     // ============================================================     function Sprite_FormationActor() { this.initialize(...arguments); }     Sprite_FormationActor.prototype = Object.create(Sprite.prototype);     Sprite_FormationActor.prototype.constructor = Sprite_FormationActor;     Sprite_FormationActor.prototype.initialize = function(actor, faceLeft) {         Sprite.prototype.initialize.call(this);         this._actor    = actor || null;         this._faceLeft = !!faceLeft;         this.anchor.x = 0.5;         this.anchor.y = 1.0;         this.z = 10;         this.loadBitmap();     };     Sprite_FormationActor.prototype.setActor = function(actor) {         if (this._actor === actor) return;         this._actor = actor || null;         this.loadBitmap();     };     Sprite_FormationActor.prototype.loadBitmap = function() {         if (!this._actor || !this._actor.battlerName()) {             this.bitmap = null; this.visible = false; return;         }         this.visible = true;         this.bitmap = ImageManager.loadSvActor(this._actor.battlerName());         this.scale.x = this._faceLeft ? 1 : -1;         this.bitmap.addLoadListener(() => this.updateFrame());         this.updateFrame();     };     Sprite_FormationActor.prototype.updateFrame = function() {         if (!this._actor || !this.bitmap || !this.bitmap.width) return;         const pw = Math.floor(this.bitmap.width  / 9);         const ph = Math.floor(this.bitmap.height / 6);         if (!pw || !ph) return;         this.setFrame(0, 1 * ph, pw, ph);     };     // ============================================================     //  СПРАЙТ «ИМЯ + HP»     // ============================================================     function Sprite_SlotInfo() { this.initialize(...arguments); }     Sprite_SlotInfo.prototype = Object.create(Sprite.prototype);     Sprite_SlotInfo.prototype.constructor = Sprite_SlotInfo;     Sprite_SlotInfo.prototype.initialize = function() {         Sprite.prototype.initialize.call(this);         this._actor = null;         const L = LAYOUT.infoLabel;         this._bitmap = new Bitmap(L.w, L.h);         this.bitmap = this._bitmap;         this.anchor.x = 0.5;         this.anchor.y = 1.0;         this.z = 15;         this.visible = false;     };     Sprite_SlotInfo.prototype.setActor = function(actor) {         this._actor = actor || null;         this.visible = !!actor;         this.refresh();     };     Sprite_SlotInfo.prototype.refresh = function() {         const b = this._bitmap; b.clear();         if (!this._actor) return;         const L = LAYOUT.infoLabel;         const w = b.width;         b.fontSize = L.nameFontSize;         b.outlineColor = "rgba(0,0,0,0.9)"; b.outlineWidth = 4;         b.textColor = "#ffffff";         b.drawText(this._actor.name(), 0, 0, w, 20, "center");         const hp = Math.max(0, this._actor.hp);         const mhp = Math.max(1, this._actor.mhp);         const ratio = Math.max(0, Math.min(1, hp / mhp));         const margin = L.hpBarMargin;         const bw = w - margin * 2;         const bh = L.hpBarH;         const bx = margin;         const by = L.h - bh - L.hpBarBottomGap;         b.fillRect(bx - 1, by - 1, bw + 2, bh + 2, "rgba(0,0,0,0.85)");         b.fillRect(bx, by, bw, bh, "rgba(40,40,40,0.9)");         const color = ratio > 0.5 ? "#3ad13a" : ratio > 0.25 ? "#e0c020" : "#e03030";         b.fillRect(bx, by, Math.floor(bw * ratio), bh, color);         b.fontSize = L.hpFontSize;         b.outlineColor = "rgba(0,0,0,0.95)"; b.outlineWidth = 3;         b.textColor = "#ffffff";         b.drawText(`${hp}/${mhp}`, 0, by - 4, w, 16, "center");     };     // ============================================================     //  ЛЕВАЯ ПАНЕЛЬ «ВЫБОР ЛИНИИ»     // ============================================================     function Window_FormationLines() { this.initialize(...arguments); }     Window_FormationLines.prototype = Object.create(Window_Base.prototype);     Window_FormationLines.prototype.constructor = Window_FormationLines;     Window_FormationLines.prototype.initialize = function(rect) {         Window_Base.prototype.initialize.call(this, rect);         this._currentLine = 0;         this.refresh();     };     Window_FormationLines.prototype.setCurrentLine = function(line) {         if (this._currentLine === line) return;         this._currentLine = line;         this.refresh();     };     Window_FormationLines.prototype.refresh = function() {         this.contents.clear();         const names = ["Первая линия", "Вторая", "Третья", "Четвёртая", "Пятая", "Шестая"];         const lh = LAYOUT.linePanel.rowH;         for (let i = 0; i < P.partyLines; i++) {             const label = i === 0 ? "Фронт" : (names[i] || `Линия ${i + 1}`);             this.changeTextColor(i === this._currentLine ? "#ffe66a" : "#ffffff");             this.contents.outlineColor = "rgba(0,0,0,0.9)";             this.contents.outlineWidth = 4;             this.drawText(label, 0, i * lh, this.contents.width, lh, "left");         }     };     // ============================================================     //  СЦЕНА ПОСТРОЕНИЯ     // ============================================================     function Scene_FormationGrid() { this.initialize(...arguments); }     Scene_FormationGrid.prototype = Object.create(Scene_MenuBase.prototype);     Scene_FormationGrid.prototype.constructor = Scene_FormationGrid;     Scene_FormationGrid.prototype.create = function() {         Scene_MenuBase.prototype.create.call(this);         this._slotCount  = P.partySize * P.partyLines;         this._cursorSlot = 0;         this._pickedSlot = -1;         this._slots      = this.buildInitialSlots();         this.createGridLayer();         this.createLabelsLayer();         this.createActorSprites();         this.createInfoSprites();         this.createCursorSprite();         this.createHelpWindow();         this.createLineWindow();         this.refreshActors();         this.updateCursorPosition();     };     Scene_FormationGrid.prototype.buildInitialSlots = function() {         const slots = new Array(this._slotCount).fill(null);         const map = $gameParty._formationSlots || {};         const members = $gameParty.members();         for (const a of members) {             const key = String(a.actorId());             if (Object.prototype.hasOwnProperty.call(map, key)) {                 const s = Number(map[key]);                 if (s >= 0 && s < this._slotCount && !slots[s]) slots[s] = a;             }         }         let free = 0;         for (const a of members) {             if (slots.indexOf(a) >= 0) continue;             while (free < this._slotCount && slots[free]) free++;             if (free >= this._slotCount) break;             slots[free++] = a;         }         return slots;     };     Scene_FormationGrid.prototype.slotPos = function(slot) {         const G = LAYOUT.grid;         const member = slot % P.partySize;         const line  = Math.floor(slot / P.partySize);         const dir    = P.partyMirror ? -1 : 1;         return {             x: G.originX + line  * G.stepX * dir,             y: G.originY - member * G.stepY         };     };     Scene_FormationGrid.prototype.helpWindowRect = function() {         const H = LAYOUT.helpWindow;         return new Rectangle(H.x, H.y, H.w, H.h);     };     Scene_FormationGrid.prototype.createLineWindow = function() {         const L = LAYOUT.linePanel;         const rect = new Rectangle(L.x, L.y, L.w, L.h);         this._lineWindow = new Window_FormationLines(rect);         this.addChild(this._lineWindow);     };     Scene_FormationGrid.prototype.createGridLayer = function() {         const G = LAYOUT.grid;         const ip = G.innerPadding;         const cw = G.cellW;         const ch = G.cellH;         let maxX = Graphics.boxWidth;         let maxY = Graphics.boxHeight;         for (let i = 0; i < this._slotCount; i++) {             const p = this.slotPos(i);             maxX = Math.max(maxX, p.x + cw);             maxY = Math.max(maxY, p.y + ch);         }         const bmp = new Bitmap(maxX, maxY);         bmp.paintOpacity = 60;         for (let i = 0; i < this._slotCount; i++) {             const p = this.slotPos(i);             bmp.fillRect(                 Math.round(p.x - cw / 2 + ip),                 Math.round(p.y - ch + ip),                 cw - ip * 2, ch - ip * 2, "white");         }         bmp.paintOpacity = 30;         for (let m = 0; m < P.partySize; m++) {             const p = this.slotPos(m);             bmp.fillRect(                 Math.round(p.x - cw / 2 + ip),                 Math.round(p.y - ch + ip),                 cw - ip * 2, ch - ip * 2, "#ffe66a");         }         this._gridSprite = new Sprite(bmp);         this._gridSprite.z = 1;         this.addChild(this._gridSprite);     };     Scene_FormationGrid.prototype.createLabelsLayer = function() {         const F  = LAYOUT.frontLabel;         const LL = LAYOUT.lineLabel;         const G  = LAYOUT.grid;         let maxX = Graphics.boxWidth;         let maxY = Graphics.boxHeight;         for (let i = 0; i < this._slotCount; i++) {             const p = this.slotPos(i);             maxX = Math.max(maxX, p.x + G.cellW / 2 + LL.w / 2 + 20);             maxY = Math.max(maxY, p.y + G.cellH + 20);         }         maxX = Math.max(maxX, F.x + F.charW + 20);         maxY = Math.max(maxY, F.y + F.charH * 6 + 20);         const bmp = new Bitmap(maxX, maxY);         bmp.outlineColor = "rgba(0,0,0,0.9)";         bmp.outlineWidth = 4;         const text = "ФРОНТ";         bmp.fontSize = F.fontSize;         bmp.textColor = "#ffe66a";         for (let i = 0; i < text.length; i++) {             bmp.drawText(text[i], F.x, F.y + i * F.charH, F.charW, F.charH, "center");         }         bmp.fontSize = LL.fontSize;         const names = ["Первая линия", "Вторая", "Третья", "Четвёртая", "Пятая", "Шестая"];         for (let line = 0; line < P.partyLines; line++) {             const topSlot = line * P.partySize + (P.partySize - 1);             const p = this.slotPos(topSlot);             const label = names[line] || `Линия ${line + 1}`;             bmp.textColor = line === 0 ? "#ffe66a" : "#9ad1ff";             bmp.drawText(label, p.x - LL.w / 2, p.y - G.cellH - LL.offsetY, LL.w, LL.h, "center");         }         this._labelsSprite = new Sprite(bmp);         this._labelsSprite.z = 5;         this.addChild(this._labelsSprite);     };     Scene_FormationGrid.prototype.createActorSprites = function() {         this._actorSprites = [];         for (let i = 0; i < this._slotCount; i++) {             const spr = new Sprite_FormationActor(null, P.partyFaceLeft);             const p = this.slotPos(i);             spr.x = p.x; spr.y = p.y; spr.visible = false;             this.addChild(spr);             this._actorSprites.push(spr);         }     };     Scene_FormationGrid.prototype.createInfoSprites = function() {         this._infoSprites = [];         for (let i = 0; i < this._slotCount; i++) {             const info = new Sprite_SlotInfo();             const p = this.slotPos(i);             info.x = p.x;             info.y = p.y - LAYOUT.infoLabel.offsetY;             this.addChild(info);             this._infoSprites.push(info);         }     };     Scene_FormationGrid.prototype.refreshActors = function() {         for (let i = 0; i < this._slotCount; i++) {             const spr = this._actorSprites[i];             const info = this._infoSprites[i];             const actor = this._slots[i];             const picked = (i === this._pickedSlot);             if (actor) {                 spr.setActor(actor); spr.visible = true;                 spr.opacity = picked ? 160 : 255;                 info.setActor(actor);                 info.opacity = picked ? 140 : 255;             } else {                 spr.visible = false;                 info.visible = false;             }         }     };     Scene_FormationGrid.prototype.createCursorSprite = function() {         const C = LAYOUT.cursor;         const G = LAYOUT.grid;         const cw = G.cellW + C.extra;         const ch = G.cellH + C.extra;         const th = C.thickness;         const bmp = new Bitmap(cw, ch);         bmp.fillRect(0, 0, cw, th, "rgba(255,230,90,0.95)");         bmp.fillRect(0, ch - th, cw, th, "rgba(255,230,90,0.95)");         bmp.fillRect(0, 0, th, ch, "rgba(255,230,90,0.95)");         bmp.fillRect(cw - th, 0, th, ch, "rgba(255,230,90,0.95)");         this._cursorSprite = new Sprite(bmp);         this._cursorSprite.anchor.x = 0.5;         this._cursorSprite.anchor.y = 0.5;         this._cursorSprite.z = 20;         this.addChild(this._cursorSprite);     };     Scene_FormationGrid.prototype.updateCursorPosition = function() {         const p = this.slotPos(this._cursorSlot);         const G = LAYOUT.grid;         this._cursorSprite.x = p.x;         this._cursorSprite.y = p.y - G.cellH / 2;         if (this._lineWindow) {             const line = Math.floor(this._cursorSlot / P.partySize);             this._lineWindow.setCurrentLine(line);         }     };     Scene_FormationGrid.prototype.createHelpWindow = function() {         const rect = this.helpWindowRect();         this._helpWindow = new Window_Help(rect);         this._helpWindow.setText(this.defaultHelpText());         this.addChild(this._helpWindow);     };     Scene_FormationGrid.prototype.defaultHelpText = function() {         return "Стрелки: перемещение по сетке  OK: взять/поставить (обмен)  Esc: выход";     };     Scene_FormationGrid.prototype.update = function() {         Scene_MenuBase.prototype.update.call(this);         if (this._cursorSprite) this.processInput();     };     Scene_FormationGrid.prototype.processInput = function() {         if (Input.isRepeated("up"))    this.moveCursor(0, -1);         if (Input.isRepeated("down"))  this.moveCursor(0,  1);         if (Input.isRepeated("left"))  this.moveCursor(-1, 0);         if (Input.isRepeated("right")) this.moveCursor( 1, 0);         if (Input.isTriggered("ok"))    this.onOk();         if (Input.isTriggered("cancel")) this.onCancel();     };     Scene_FormationGrid.prototype.moveCursor = function(dx, dy) {         let line  = Math.floor(this._cursorSlot / P.partySize);         let member = this._cursorSlot % P.partySize;         const lineDelta = P.partyMirror ? -dx : dx;         line  = (line + lineDelta + P.partyLines) % P.partyLines;         member = (member - dy + P.partySize) % P.partySize;         this._cursorSlot = line * P.partySize + member;         this.updateCursorPosition();         SoundManager.playCursor();     };     Scene_FormationGrid.prototype.onOk = function() {         if (this._pickedSlot < 0) {             if (this._slots[this._cursorSlot]) {                 this._pickedSlot = this._cursorSlot;                 SoundManager.playOk();                 this.refreshActors();                 this._helpWindow.setText("OK: поставить / обменять  Esc: отмена");             }         } else {             const a = this._pickedSlot, b = this._cursorSlot;             const tmp = this._slots[a];             this._slots[a] = this._slots[b];             this._slots[b] = tmp;             this._pickedSlot = -1;             SoundManager.playOk();             this.refreshActors();             this._helpWindow.setText(this.defaultHelpText());         }     };     Scene_FormationGrid.prototype.onCancel = function() {         if (this._pickedSlot >= 0) {             this._pickedSlot = -1;             this.refreshActors();             this._helpWindow.setText(this.defaultHelpText());             SoundManager.playCancel();         } else {             this.commitFormation();             SoundManager.playCancel();             this.popScene();         }     };     Scene_FormationGrid.prototype.commitFormation = function() {         const map = $gameParty._formationSlots || ($gameParty._formationSlots = {});         for (const k in map) delete map[k];         const ids = [];         for (let i = 0; i < this._slotCount; i++) {             const a = this._slots[i];             if (a) {                 map[String(a.actorId())] = i;                 ids.push(a.actorId());             }         }         for (const a of $gameParty.allMembers()) {             const key = String(a.actorId());             if (!Object.prototype.hasOwnProperty.call(map, key)) ids.push(a.actorId());         }         $gameParty._actors = ids;     };     Scene_Menu.prototype.commandFormation = function() {         if ($gameParty.size() >= 1) SceneManager.push(Scene_FormationGrid);     };     Window_MenuCommand.prototype.addFormationCommand = function() {         if (this.needsCommand("formation")) {             this.addCommand(TextManager.formation, "formation", $gameParty.size() >= 1);         }     };     PluginManager.registerCommand(pluginName, "openFormation", () => {         if (SceneManager._scene instanceof Scene_Battle) return;         SceneManager.push(Scene_FormationGrid);     });     // ============================================================     //  «СЛИШКОМ ДАЛЕКО!» — модальное сообщение, ждёт клика     // ============================================================     const TOO_FAR_TEXT = "Слишком далеко! Цель в недоступной линии.";     // Аккуратно достаём текущий action.     function currentTargetingAction() {         if (typeof BattleManager.inputtingAction === "function") {             const a = BattleManager.inputtingAction();             if (a) return a;         }         if (BattleManager._inputtingAction) return BattleManager._inputtingAction;         if (BattleManager._action) return BattleManager._action;         const actor = BattleManager.actor && BattleManager.actor();         if (actor && actor.currentAction) {             const a = actor.currentAction();             if (a) return a;         }         return null;     }     // Показываем сообщение и временно деактивируем окно цели,     // чтобы то же OK не пролетело в него.     Scene_Battle.prototype.showTooFarMessage = function(text) {         const active = this._enemyWindow && this._enemyWindow.active ? this._enemyWindow                     : this._actorWindow && this._actorWindow.active ? this._actorWindow                     : null;         if (active) {             this._tooFarRestore = { win: active, index: active.index() };             active.deactivate();         }         SoundManager.playBuzzer();         $gameMessage.add(text || TOO_FAR_TEXT);     };     // Возвращаем фокус окну после того, как игрок кликнул по сообщению.     const _Scene_Battle_update = Scene_Battle.prototype.update;     Scene_Battle.prototype.update = function() {         _Scene_Battle_update.call(this);         if (this._tooFarRestore && !$gameMessage.isBusy()) {             const r = this._tooFarRestore;             this._tooFarRestore = null;             if (r.win && r.win.visible) {                 r.win.activate();                 r.win.select(Math.min(r.index, r.win.maxItems() - 1));             }         }     };     // Перехват OK в окне врагов.     if (typeof Window_BattleEnemy !== "undefined") {         const _WinEnemy_processOk = Window_BattleEnemy.prototype.processOk;         Window_BattleEnemy.prototype.processOk = function() {             if ($gameMessage.isBusy()) return;             const action = currentTargetingAction();             const enemy = (typeof this.enemy === "function") ? this.enemy() : null;             const reason = meleeBlockReason(action, enemy);             if (reason) {                 SceneManager._scene.showTooFarMessage(reason);                 return;             }             _WinEnemy_processOk.call(this);         };     }     // Симметрично для окна союзников.     if (typeof Window_BattleActor !== "undefined") {         const _WinActor_processOk = Window_BattleActor.prototype.processOk;         Window_BattleActor.prototype.processOk = function() {             if ($gameMessage.isBusy()) return;             const action = currentTargetingAction();             const actor = (typeof this.actor === "function") ? this.actor() : null;             const reason = meleeBlockReason(action, actor);             if (reason) {                 SceneManager._scene.showTooFarMessage(reason);                 return;             }             _WinActor_processOk.call(this);         };     }     // ============================================================     //  ЭКСПОРТ     // ============================================================     window.BattleFormationGrid = {         config: P,         layout: LAYOUT,         scene:  Scene_FormationGrid     }; })();
Code:
/*: * @target MZ * @plugindesc v3.0 Formation Reinforcements: волны + роли melee/ranged + респавн с нуля + диагностика * @author Custom * * @help * ============================================================ *  Battle Formation Reinforcements v3.0 * ============================================================ * Требует BattleFormationGrid.js (v5.7+). * Совместим с CustomEnemySV.js (SVSheet / Weapon / SimpleSheet). * * --- ЧТО ДЕЛАТЬ, ЕСЛИ НЕ РАБОТАЕТ --- *  1) Открой консоль (F12) → начни бой. *  2) Ищи строку: [BFG] BattleFormationReinforcements v3.0 loaded ... *     Если её нет — плагин не загружен (порядок в списке / имя файла / отключён). *  3) Ищи строку: [BFG] setup: cfg={...} *     Там видно, что именно распарсилось из note. *  4) Каждый ход пишет: [BFG] tick: turn=... alive=.../cap empty=[...] want=... n=... *     Если там interval=0 — тег не распознан, поставь интервал явно. * * --- ЛОГИКА ВОЛН --- *  - Каждая волна = <BFG_count:N> (или меньше, если не влезает). *  - <BFG_interval:N> — каждые N ходов. Дефолт: 1 (волны идут по ходам). *    <BFG_interval:0> — только экстренный спавн при полной пустоте арены. *  - Если волна не влезает целиком — спавним ЧАСТЬ, остаток добьётся позже. * * --- ПРИОРИТЕТ ЯЧЕЕК (в Note врага) --- *  <cellPriority:N> / <cellLine:N> — предпочитаемая линия (0-индексация). *  <cellSlot:N>                    — конкретная ячейка. * * --- РОЛИ (в Note врага) --- *  <cellRole:melee>  / <cellMelee>   — ближник. *  <cellRole:ranged> / <cellRanged>  — дальник. *  Без роли — враг занимает линию с наименьшим числом живых. * * --- НАСТРОЙКА В NOTE ОТРЯДА (или Комментарии события битвы) --- *  <BFG_maxEnemies:N>          макс. врагов ОДНОВРЕМЕННО (cap = min(N, cells)) *  <BFG_delay:N>               первое подкрепление через N ходов (деф. 1) *  <BFG_interval:N>            каждые N ходов (деф. 1; 0 = только аварийный) *  <BFG_count:N>               сколько за раз (деф. 1) *  <BFG_maxReinforcements:N>   лимит всего подкреплений (0 = ∞) *  <BFG_se:Name>               SE-файл (из audio/se/) *  <BFG_message:Text>          сообщение при подкреплении *  <BFG_pool:1,3,5>            пул ID врагов (иначе состав отряда) *  <BFG_noAutoFill>            отключить автозаполнение *  <BFG_noReinforce>           отключить подкрепления *  <BFG_noReposition>          отключить шагание melee/ranged * ============================================================ * * @command spawn * @text Немедленно вызвать подкрепление * @arg count * @text Сколько * @type number * @min 1 * @default 1 * * @command resetTimer * @text Сбросить таймер подкреплений * * @param enabled * @text Плагин включён * @type boolean * @default true * * @param autoFill * @text Автозаполнение ячеек при старте * @type boolean * @default true * * @param seVolume * @text Громкость SE подкреплений * @type number * @min 0 * @max 100 * @default 80 * * @param debug * @text Логи в консоль (F12) * @type boolean * @default true */ (() => {     "use strict";     const pluginName = "BattleFormationReinforcements";     const BFG = window.BattleFormationGrid;     if (!BFG || !BFG.config) {         console.error(`[${pluginName}] Требуется BattleFormationGrid.js (v5.7+). ` +                       `Проверь порядок плагинов: BFG должен стоять ВЫШЕ подкреплений.`);         return;     }     const raw = PluginManager.parameters(pluginName);     const toBool = (v, def) => (v === undefined ? def : String(v) !== "false");     const toNum  = (v, def) => { const n = Number(v); return isNaN(n) ? def : n; };     const P = {         enabled:  toBool(raw.enabled, true),         autoFill: toBool(raw.autoFill, true),         seVolume: Math.max(0, Math.min(100, Math.floor(toNum(raw.seVolume, 80)))),         debug:    toBool(raw.debug, true)     };     const log  = (...a) => { if (P.debug) console.log("%c[BFG]", "color:#8cf;font-weight:bold", ...a); };     const warn = (...a) => { if (P.debug) console.warn("%c[BFG]", "color:#fc8;font-weight:bold", ...a); };     const MAX_CELLS  = () => BFG.config.enemySize * BFG.config.enemyLines;     const CELL_SIZE  = () => BFG.config.enemySize;     const CELL_LINES = () => BFG.config.enemyLines;     // Громкая загрузочная строка — если её нет в консоли, плагин не поднялся.     log(`BattleFormationReinforcements v3.0 loaded. ` +         `Поле: ${CELL_SIZE()}x${CELL_LINES()}=${MAX_CELLS()} ячеек. ` +         `enabled=${P.enabled} autoFill=${P.autoFill} debug=${P.debug}`);     // ============================================================     //  NOTE ВРАГА     // ============================================================     function getEnemyNote(enemyId) {         const e = $dataEnemies[enemyId];         return (e && e.note) || "";     }     function getEnemyPreferredLine(enemyId) {         const note = getEnemyNote(enemyId);         const m = /<cellPriority[:\s]\s*(\d+)\s*>/i.exec(note)                || /<cellLine[:\s]\s*(\d+)\s*>/i.exec(note);         if (!m) return -1;         const line = Math.max(0, Number(m[1]));         return line < CELL_LINES() ? line : -1;     }     function getEnemyPreferredSlot(enemyId) {         const note = getEnemyNote(enemyId);         const m = /<cellSlot[:\s]\s*(\d+)\s*>/i.exec(note);         if (!m) return -1;         const slot = Math.max(0, Number(m[1]));         return slot < MAX_CELLS() ? slot : -1;     }     function getEnemyRole(enemyId) {         const note = getEnemyNote(enemyId);         if (/<cellRole[:\s]\s*(melee|ближник)\s*>/i.test(note) || /<cellMelee>/i.test(note))  return "melee";         if (/<cellRole[:\s]\s*(ranged|дальник|range)\s*>/i.test(note) || /<cellRanged>/i.test(note)) return "ranged";         return null;     }     // ============================================================     //  ЧТЕНИЕ NOTE ОТРЯДА     // ============================================================     function parseTroopConfig(troop) {         let note = (troop && troop.note) || "";         if (troop && troop.pages) {             for (const page of troop.pages) {                 if (page.list) {                     for (const cmd of page.list) {                         if (cmd.code === 108 || cmd.code === 408) {                             note += " " + cmd.parameters[0];                         }                     }                 }             }         }         const num = (re, def) => {             const m = re.exec(note);             if (!m) return def;             const n = Number(m[1]);             return isNaN(n) ? def : n;         };         const has = (re) => re.test(note);         // ВАЖНО: interval по умолчанию = 1 (волны идут по ходам).         // Если нужно «только аварийный спавн» — задай <BFG_interval:0> явно.         const cfg = {             maxEnemies:        Math.max(0, Math.floor(num(/<BFG[_:]maxEnemies[:\s]\s*(\d+)\s*>/i, 0))),             delay:             Math.max(0, Math.floor(num(/<BFG[_:]delay[:\s]\s*(\d+)\s*>/i, 1))),             interval:          Math.max(0, Math.floor(num(/<BFG[_:]interval[:\s]\s*(\d+)\s*>/i, 1))),             count:             Math.max(1, Math.floor(num(/<BFG[_:]count[:\s]\s*(\d+)\s*>/i, 1))),             maxReinforcements: Math.max(0, Math.floor(num(/<BFG[_:]maxReinforcements[:\s]\s*(\d+)\s*>/i, 0))),             se: "",             message: "",             pool: [],             noAutoFill:   false,             noReinforce:  false,             noReposition: false         };         const seM = /<BFG[_:]se[:\s]\s*([^>\s]+)\s*>/i.exec(note);         if (seM) cfg.se = seM[1];         const msgM = /<BFG[_:]message[:\s]\s*([^>]+?)\s*>/i.exec(note);         if (msgM) cfg.message = msgM[1].trim();         const poolM = /<BFG[_:]pool[:\s]\s*([\d,\s]+)\s*>/i.exec(note);         if (poolM) {             cfg.pool = poolM[1].split(",")                 .map(s => Number(s.trim()))                 .filter(n => n > 0);         }         if (has(/<BFG[_:]noAutoFill>/i))   cfg.noAutoFill   = true;         if (has(/<BFG[_:]noReinforce>/i))  cfg.noReinforce  = true;         if (has(/<BFG[_:]noReposition>/i)) cfg.noReposition = true;         log(`setup: note="${note.trim()}"`);         log(`setup: cfg=`, JSON.parse(JSON.stringify(cfg)));         const cells = MAX_CELLS();         if (cfg.maxEnemies > cells) {             warn(`<BFG_maxEnemies:${cfg.maxEnemies}> > числа ячеек (${cells}). ` +                  `Фактический cap = ${cells}.`);         }         if (cfg.interval <= 0 && !cfg.noReinforce) {             warn(`<BFG_interval> = 0 — волны по ходам отключены, работает только аварийный спавн.`);         }         if (cfg.noReinforce) {             warn(`<BFG_noReinforce> — подкрепления полностью отключены для этого боя.`);         }         return cfg;     }     function totalCapOf(cfg) {         const cells = MAX_CELLS();         if (cfg && cfg.maxEnemies > 0) return Math.min(cfg.maxEnemies, cells);         return cells;     }     // ============================================================     //  СОСТОЯНИЕ     // ============================================================     function ensureState() {         const t = $gameTroop;         if (!t) return null;         if (!t._reinfState) {             t._reinfState = {                 pool: [],                 spawned: 0,                 cfg: null,                 firstSpawnDone: false,                 lastSpawnTurn: 0,                 _lastTick: -1             };         }         return t._reinfState;     }     function ensureCfg() {         const t = $gameTroop;         if (!t) return null;         let st = t._reinfState;         if (!st || !st.cfg) {             st = ensureState();             if (!st) return null;             if (!st.cfg) {                 const troop = $dataTroops[t._troopId];                 if (troop) {                     st.cfg = parseTroopConfig(troop);                     if (!st.pool || !st.pool.length) {                         const ids = t._enemies.map(e => e.enemyId());                         st.pool = ids.length ? ids : [1];                     }                     log(`cfg восстановлен по _troopId=${t._troopId}`);                 }             }         }         return st;     }     // ============================================================     //  РАСПРЕДЕЛЕНИЕ ПО ЯЧЕЙКАМ (setup)     // ============================================================     function assignEnemiesToSlots(ids, maxCells, size) {         const result = new Array(maxCells).fill(0);         const used = new Set();         const priority = [];         const normal = [];         for (let i = 0; i < ids.length; i++) {             const id = ids[i];             const slot = getEnemyPreferredSlot(id);             const line = getEnemyPreferredLine(id);             if (slot >= 0)      priority.push({ id, slot, line: -1, order: i });             else if (line >= 0) priority.push({ id, slot: -1, line,    order: i });             else                normal.push({ id, order: i });         }         for (const p of priority) {             if (p.slot >= 0 && !used.has(p.slot)) {                 result[p.slot] = p.id;                 used.add(p.slot);                 continue;             }             if (p.line >= 0) {                 const start = p.line * size;                 let placed = false;                 for (let m = 0; m < size; m++) {                     const s = start + m;                     if (s < maxCells && !used.has(s)) {                         result[s] = p.id;                         used.add(s);                         placed = true;                         break;                     }                 }                 if (placed) continue;             }             normal.push(p);         }         normal.sort((a, b) => a.order - b.order);         for (const n of normal) {             for (let s = 0; s < maxCells; s++) {                 if (!used.has(s)) {                     result[s] = n.id;                     used.add(s);                     break;                 }             }         }         return result;     }     // ============================================================     //  SETUP     // ============================================================     const _Game_Troop_setup = Game_Troop.prototype.setup;     Game_Troop.prototype.setup = function(troopId) {         _Game_Troop_setup.call(this, troopId);         this._reinfState = null;         if (!P.enabled) return;         const troop = $dataTroops[troopId];         const cfg = parseTroopConfig(troop);         const st = ensureState();         if (!st) return;         st.cfg = cfg;         if (cfg.pool && cfg.pool.length) {             st.pool = cfg.pool.slice();         } else {             const ids = this._enemies.map(e => e.enemyId());             st.pool = ids.length ? ids : [1];         }         const maxCells = MAX_CELLS();         const size     = CELL_SIZE();         const cap      = totalCapOf(cfg);         const sourceIds = this._enemies.map(e => e.enemyId());         if (!sourceIds.length) sourceIds.push(st.pool[0] || 1);         const autoFill = P.autoFill && !cfg.noAutoFill;         const fillList = sourceIds.slice();         if (autoFill) {             while (fillList.length < cap) {                 fillList.push(st.pool[(fillList.length - sourceIds.length) % st.pool.length]);             }         }         const slotToId = assignEnemiesToSlots(fillList, maxCells, size);         this._enemies = [];         for (let s = 0; s < maxCells; s++) {             const id = slotToId[s];             const enemy = new Game_Enemy(id > 0 ? id : st.pool[0], 0, 0);             enemy._formationSlotIndex = s;             if (id > 0) {                 enemy._reinforcementPending = false;             } else {                 enemy._reinforcementPending = true;                 enemy.setHp(0);                 enemy.hide();             }             this._enemies.push(enemy);         }         const liveAtStart = this._enemies.filter(e => !e._reinforcementPending).length;         log(`setup: autoFill=${autoFill} cap=${cap} cells=${maxCells} ` +             `заполнено при старте=${liveAtStart} ` +             `(из них «живых» врагов: ${this._enemies.filter(e => !e._reinforcementPending).length}, ` +             `пустых слотов: ${maxCells - liveAtStart})`);     };     const _Game_Enemy_index = Game_Enemy.prototype.index;     Game_Enemy.prototype.index = function() {         if (this._formationSlotIndex != null) return this._formationSlotIndex;         return _Game_Enemy_index.call(this);     };     // ============================================================     //  СПРАЙТЫ     // ============================================================     function destroyEnemySprite(sp) {         if (!sp) return;         for (const key of ["_shadowSprite", "_weaponSprite"]) {             const sib = sp[key];             if (sib) {                 if (sib.parent) sib.parent.removeChild(sib);                 if (typeof sib.destroy === "function") sib.destroy();                 sp[key] = null;             }         }         if (sp.parent) sp.parent.removeChild(sp);         if (typeof sp.destroy === "function") sp.destroy();     }     function recreateEnemySprite(enemy) {         const scene = SceneManager._scene;         if (!(scene instanceof Scene_Battle)) return;         const ss = scene._spriteset;         if (!ss || !ss._enemySprites) return;         const idx = ss._enemySprites.findIndex(sp => sp._enemy === enemy);         if (idx < 0) {             const sp = new Sprite_Enemy(enemy);             ss._enemySprites.push(sp);             if (ss._battleField) ss._battleField.addChild(sp);         } else {             const old = ss._enemySprites[idx];             const parent = old.parent;             destroyEnemySprite(old);             const sp = new Sprite_Enemy(enemy);             ss._enemySprites[idx] = sp;             const targetParent = parent || ss._battleField;             if (targetParent) targetParent.addChild(sp);         }         if (typeof ss.updateEnemySprites === "function") {             ss.updateEnemySprites();         }     }     function reviveEnemyInSlot(enemy, newEnemyId) {         if (newEnemyId && newEnemyId !== enemy.enemyId()) {             enemy._enemyId = newEnemyId;         }         enemy._hidden = false;         enemy._reinforcementPending = false;         enemy.clearStates();         enemy.clearBuffs();         enemy.clearActions();         enemy.setHp(enemy.mhp);         enemy.setMp(enemy.mmp);         enemy.refresh();         recreateEnemySprite(enemy);     }     function findEmptySlots() {         const max = MAX_CELLS();         const enemies = $gameTroop._enemies;         const res = [];         for (let i = 0; i < max; i++) {             const e = enemies[i];             if (!e) { res.push(i); continue; }             if (e._reinforcementPending) { res.push(i); continue; }             if (e.isDead() || e.hp <= 0 || e.isHidden()) { res.push(i); continue; }         }         return res;     }     // ============================================================     //  ВЫБОР СЛОТА     // ============================================================     function pickSlotForEnemy(enemyId, empty, size) {         const prefSlot = getEnemyPreferredSlot(enemyId);         if (prefSlot >= 0 && empty.includes(prefSlot)) return prefSlot;         const prefLine = getEnemyPreferredLine(enemyId);         if (prefLine >= 0) {             const inLine = empty.find(s => Math.floor(s / size) === prefLine);             if (inLine !== undefined) return inLine;             let bestSlot = -1, bestDist = Infinity;             for (const s of empty) {                 const d = Math.abs(Math.floor(s / size) - prefLine);                 if (d < bestDist) { bestDist = d; bestSlot = s; }             }             if (bestSlot >= 0) return bestSlot;         }         const role = getEnemyRole(enemyId);         if (role === "melee") {             let best = -1, bestLine = Infinity;             for (const s of empty) {                 const L = Math.floor(s / size);                 if (L < bestLine) { bestLine = L; best = s; }             }             if (best >= 0) return best;         }         if (role === "ranged") {             let best = -1, bestLine = -Infinity;             for (const s of empty) {                 const L = Math.floor(s / size);                 if (L > bestLine) { bestLine = L; best = s; }             }             if (best >= 0) return best;         }         const lines = CELL_LINES();         const occupied = new Array(lines).fill(0);         const enemies = $gameTroop._enemies;         const maxCells = MAX_CELLS();         for (let s = 0; s < maxCells; s++) {             const e = enemies[s];             if (e && !e._reinforcementPending && e.isAlive()) {                 occupied[Math.floor(s / size)]++;             }         }         let best = -1, bestCount = Infinity;         for (const s of empty) {             const L = Math.floor(s / size);             if (occupied[L] < bestCount) { bestCount = occupied[L]; best = s; }         }         if (best >= 0) return best;         return empty[0];     }     // ============================================================     //  ПЕРЕМЕЩЕНИЕ     // ============================================================     function moveEnemyToSlot(enemy, newSlot) {         const oldSlot = enemy._formationSlotIndex;         if (oldSlot === newSlot) return false;         const enemies = $gameTroop._enemies;         const target = enemies[newSlot];         if (target && !target._reinforcementPending && target.isAlive()) return false;         let placeholder;         if (target && target._reinforcementPending) {             placeholder = target;             placeholder._formationSlotIndex = oldSlot;         } else {             placeholder = new Game_Enemy(enemy.enemyId(), 0, 0);             placeholder._formationSlotIndex = oldSlot;             placeholder._reinforcementPending = true;             placeholder.setHp(0);             placeholder.hide();         }         enemy._formationSlotIndex = newSlot;         enemies[newSlot] = enemy;         enemies[oldSlot] = placeholder;         recreateEnemySprite(enemy);         recreateEnemySprite(placeholder);         return true;     }     function repositionEnemies() {         const st = $gameTroop._reinfState;         if (!st || !st.cfg) return;         if (st.cfg.noReposition) return;         const size = CELL_SIZE();         const lines = CELL_LINES();         const maxCells = MAX_CELLS();         const enemies = $gameTroop._enemies;         const aliveByLine = [];         for (let L = 0; L < lines; L++) aliveByLine[L] = 0;         const list = [];         for (let s = 0; s < maxCells; s++) {             const e = enemies[s];             if (e && !e._reinforcementPending && e.isAlive()) {                 list.push(e);                 aliveByLine[Math.floor(s / size)]++;             }         }         const emptySet = new Set(findEmptySlots());         for (const e of list) {             const role = getEnemyRole(e.enemyId());             if (!role) continue;             const curSlot = e._formationSlotIndex;             const curLine = Math.floor(curSlot / size);             if (role === "melee") {                 if (curLine <= 0) continue;                 const targetLine = curLine - 1;                 const start = targetLine * size;                 let found = -1;                 for (let k = 0; k < size; k++) {                     const s = start + k;                     if (emptySet.has(s)) { found = s; break; }                 }                 if (found >= 0 && moveEnemyToSlot(e, found)) {                     emptySet.delete(found); emptySet.add(curSlot);                     aliveByLine[curLine]--; aliveByLine[targetLine]++;                 }             } else if (role === "ranged") {                 if (curLine >= lines - 1) continue;                 let allEmptyAhead = true;                 for (let L = 0; L < curLine; L++) {                     if (aliveByLine[L] > 0) { allEmptyAhead = false; break; }                 }                 if (!allEmptyAhead) continue;                 const targetLine = curLine + 1;                 const start = targetLine * size;                 let found = -1;                 for (let k = 0; k < size; k++) {                     const s = start + k;                     if (emptySet.has(s)) { found = s; break; }                 }                 if (found >= 0 && moveEnemyToSlot(e, found)) {                     emptySet.delete(found); emptySet.add(curSlot);                     aliveByLine[curLine]--; aliveByLine[targetLine]++;                 }             }         }     }     // ============================================================     //  СПАВН     // ============================================================     function doReinforce(count, isEmergency) {         const st = ensureCfg();         if (!st || !st.cfg) { warn("doReinforce: нет cfg."); return 0; }         const cfg = st.cfg;         if (cfg.noReinforce) { log("doReinforce: noReinforce."); return 0; }         if (!isEmergency && cfg.interval <= 0) { log("doReinforce: interval=0, skip."); return 0; }         const size = CELL_SIZE();         const cap  = totalCapOf(cfg);         const aliveNow = $gameTroop._enemies.filter(e =>             e && !e._reinforcementPending && e.isAlive()         ).length;         const roomByCap = Math.max(0, cap - aliveNow);         const empty = findEmptySlots();         const baseWant = Math.max(1, Math.floor(count || cfg.count));         const budgetLeft = cfg.maxReinforcements > 0             ? Math.max(0, cfg.maxReinforcements - st.spawned)             : Infinity;         log(`doReinforce: turn=${$gameTroop.turnCount()} ` +             `alive=${aliveNow}/${cap} room=${roomByCap} empty=[${empty.join(",")}] ` +             `want=${baseWant} budgetLeft=${budgetLeft} emergency=${!!isEmergency}`);         if (roomByCap <= 0) { log("  → cap достигнут (все слоты до cap заняты)."); return 0; }         if (!empty.length)  { log("  → нет пустых ячеек физически."); return 0; }         if (budgetLeft <= 0) { log("  → бюджет исчерпан."); return 0; }         const want = Math.min(baseWant, budgetLeft);         if (want <= 0) return 0;         const canFit = Math.min(roomByCap, empty.length);         const n = Math.min(want, canFit);         if (n <= 0) return 0;         log(`  → Спавним ${n} (canFit=${canFit}, want=${want}).`);         const pool    = (st.pool && st.pool.length) ? st.pool : [1];         const enemies = $gameTroop._enemies;         let spawned = 0;         for (let k = 0; k < n; k++) {             if (!empty.length) break;             const newId = pool[(st.spawned + k) % pool.length];             const slot  = pickSlotForEnemy(newId, empty, size);             empty.splice(empty.indexOf(slot), 1);             let enemy = enemies[slot];             if (!enemy) {                 enemy = new Game_Enemy(newId, 0, 0);                 enemy._formationSlotIndex = slot;                 enemies[slot] = enemy;             }             reviveEnemyInSlot(enemy, newId);             spawned++;         }         if (spawned > 0) {             st.spawned += spawned;             if (cfg.se) AudioManager.playSe({ name: cfg.se, volume: P.seVolume, pitch: 100, pan: 0 });             if (cfg.message) $gameMessage.add(cfg.message);         }         return spawned;     }     // ============================================================     //  ТИК ХОДА (общий для двух триггеров, с защитой от дублей)     // ============================================================     function onTurnTick() {         if (!P.enabled) return;         const st = ensureCfg();         if (!st || !st.cfg) { warn("tick: cfg отсутствует."); return; }         const cfg = st.cfg;         const turn = $gameTroop.turnCount();         if (st._lastTick === turn) return; // уже обрабатывали этот ход         st._lastTick = turn;         log(`tick: turn=${turn} first=${st.firstSpawnDone} last=${st.lastSpawnTurn} ` +             `interval=${cfg.interval} delay=${cfg.delay} count=${cfg.count} ` +             `spawned=${st.spawned}`);         if (cfg.noReinforce) { log("  → noReinforce."); return; }         if (cfg.interval <= 0) { log("  → interval=0, только аварийный спавн."); return; }         if ($gameParty.isAllDead()) { log("  → party dead."); return; }         if (BattleManager._phase === "battleEnd") { log("  → battleEnd."); return; }         if (!st.firstSpawnDone) {             if (turn >= cfg.delay) {                 const spawned = doReinforce(cfg.count);                 if (spawned > 0) {                     st.firstSpawnDone = true;                     st.lastSpawnTurn = turn;                 }             }         } else {             if (turn - st.lastSpawnTurn >= cfg.interval) {                 const spawned = doReinforce(cfg.count);                 if (spawned > 0) {                     st.lastSpawnTurn = turn;                 }             }         }         repositionEnemies();     }     // --- Триггер 1: increaseTurn (стандартный MZ) ---     if (typeof Game_Troop.prototype.increaseTurn === "function") {         const _increaseTurn = Game_Troop.prototype.increaseTurn;         Game_Troop.prototype.increaseTurn = function() {             _increaseTurn.call(this);             onTurnTick();         };         log("Триггер: Game_Troop.increaseTurn — ОК");     } else {         warn("Game_Troop.increaseTurn не найден — включён только fallback на startTurn.");     }     // --- Триггер 2: BattleManager.startTurn (fallback) ---     if (typeof BattleManager.startTurn === "function") {         const _startTurn = BattleManager.startTurn;         BattleManager.startTurn = function() {             _startTurn.call(this);             onTurnTick();         };     }     // ============================================================     //  isAllDead — экстренный спавн     // ============================================================     const _Game_Troop_isAllDead = Game_Troop.prototype.isAllDead;     Game_Troop.prototype.isAllDead = function() {         if (!_Game_Troop_isAllDead.call(this)) return false;         if (!P.enabled) return true;         const st = ensureCfg();         const cfg = st && st.cfg;         if (!cfg) return true;         if (cfg.noReinforce) return true;         if (cfg.interval <= 0) return true;         if (cfg.maxReinforcements > 0 && st.spawned >= cfg.maxReinforcements) return true;         if (!st.pool || !st.pool.length) return true;         log("isAllDead: арена пуста, пробуем emergency-спавн.");         const spawned = doReinforce(cfg.count, true);         if (spawned > 0) {             st.lastSpawnTurn = this.turnCount();             return false;         }         return true;     };     // ============================================================     //  СКРЫТИЕ ОЖИДАЮЩИХ     // ============================================================     const _Spriteset_Battle_update = Spriteset_Battle.prototype.update;     Spriteset_Battle.prototype.update = function() {         _Spriteset_Battle_update.call(this);         if (!this._enemySprites) return;         for (const sprite of this._enemySprites) {             const enemy = sprite._enemy;             if (!enemy || !enemy._reinforcementPending) continue;             if (sprite.visible || sprite.opacity > 0) {                 sprite.visible = false;                 sprite.opacity = 0;             }             for (const key of ["_shadowSprite", "_weaponSprite"]) {                 const sib = sprite[key];                 if (sib && (sib.visible || sib.opacity > 0)) {                     sib.visible = false;                     sib.opacity = 0;                 }             }         }     };     // ============================================================     //  КОМАНДЫ     // ============================================================     PluginManager.registerCommand(pluginName, "spawn", args => {         const n = Math.max(1, Number(args.count) || 1);         doReinforce(n, true);     });     PluginManager.registerCommand(pluginName, "resetTimer", () => {         const st = ensureState();         if (st) { st.firstSpawnDone = false; st.lastSpawnTurn = 0; st._lastTick = -1; }     });     // ============================================================     //  ЭКСПОРТ     // ============================================================     window.BattleFormationReinforcements = {         config: P,         maxCells: MAX_CELLS,         totalCap: totalCapOf,         spawnNow: doReinforce,         findEmptySlots,         reviveEnemyInSlot,         recreateEnemySprite,         destroyEnemySprite,         moveEnemyToSlot,         repositionEnemies,         getEnemyPreferredLine,         getEnemyPreferredSlot,         getEnemyRole,         pickSlotForEnemy     }; })();
Code:
/*: * @target MZ * @plugindesc Кастомные SV-анимации врагов + тень + оружие. * @author AI Assistant * @help * ПАПКИ: *   1. Спрайты врагов: img/sv_enemies/ *   2. Оружие:         img/system/Weapons1.png ... * * СТРУКТУРА SV-ЛИСТА ВРАГА (9 колонок × 6 строк): *   Колонки 0,1,2 — idle (ряд 0) *   Колонки 3,4,5 — анимация действия в ряду <AttackRow> * * СТРУКТУРА ЛИСТА ОРУЖИЯ (6 колонок × 6 строк): *   2 блока по 3 кадра в каждой строке = 12 видов оружия (индексы 0..11). *   Чётные индексы (0,2,4,...) — левый блок (колонки 0,1,2). *   Нечётные индексы (1,3,5,...) — правый блок (колонки 3,4,5). *   Строка = Math.floor(index / 2). * * ТЕГИ: *   <SVSheet>              — файл берётся из Battle Graphic врага *   <SVSheet: filename>    — файл берётся из img/sv_enemies/filename.png *   <AttackRow: N>         — ряд атаки (0=укол, 1=замах, 2=выстрел, 5=магия) *   <Weapon: ID>           — оружие по ID из базы данных *   <Weapon: file,index>   — вручную: файл из img/system/ и индекс 0..11 *   <SimpleSheet: i> *   <SimpleSheet: i,row> */ (() => {     // ============ ПОЗИЦИОНИРОВАНИЕ СПРАЙТА ВРАГА ============     const SV_OFFSET_X = 0;     const SV_OFFSET_Y = 5;     // ============ ПОЗИЦИОНИРОВАНИЕ ОРУЖИЯ ============     const WEAPON_OFFSET_X    = -20;     const WEAPON_OFFSET_Y    = -40;     const WEAPON_SCALE       = 1.0;     const WEAPON_SHOW_ALWAYS = false;     // ============ СКОРОСТЬ АНИМАЦИЙ ============     const SPEED_IDLE   = 16;     const SPEED_ATTACK = 22;     // ============ СТРУКТУРА ЛИСТА ОРУЖИЯ ============     const WEAPON_TOTAL_COLS = 6;     const WEAPON_TOTAL_ROWS = 6;     // ============================================================     //  ХУКИ BattleManager — флаг "выполняет действие"     // ============================================================     const _startAction = BattleManager.startAction;     BattleManager.startAction = function() {         _startAction.call(this);         if (this._subject) this._subject._performingAction = true;     };     const _endAction = BattleManager.endAction;     BattleManager.endAction = function() {         if (this._subject) this._subject._performingAction = false;         _endAction.call(this);     };     // ============================================================     //  INIT     // ============================================================     const _initMembers = Sprite_Enemy.prototype.initMembers;     Sprite_Enemy.prototype.initMembers = function() {         _initMembers.call(this);         this._isSVSheet        = false;         this._isSimpleSheet    = false;         this._weaponSprite     = null;         this._shadowSprite     = null;         this._weaponFile       = null;         this._weaponIndex      = 0;         this._simpleIndex      = 1;         this._simpleRow        = 1;         this._attackRow        = 1;         this._attackStartFrame = null;         this._svFileName       = null;         this._cw               = 0;         this._ch               = 0;         this._ww               = 0;         this._wh               = 0;     };     // ============================================================     //  setBattler     // ============================================================     const _setBattler = Sprite_Enemy.prototype.setBattler;     Sprite_Enemy.prototype.setBattler = function(battler) {         if (this._shadowSprite) {             if (this._shadowSprite.parent)                 this._shadowSprite.parent.removeChild(this._shadowSprite);             this._shadowSprite = null;         }         if (this._weaponSprite) {             if (this._weaponSprite.parent)                 this._weaponSprite.parent.removeChild(this._weaponSprite);             this._weaponSprite = null;         }         _setBattler.call(this, battler);         this._isSVSheet        = false;         this._isSimpleSheet    = false;         this._weaponFile       = null;         this._weaponIndex      = 0;         this._simpleIndex      = 1;         this._simpleRow        = 1;         this._attackRow        = 1;         this._attackStartFrame = null;         this._svFileName       = null;         this._cw               = 0;         this._ch               = 0;         this._ww               = 0;         this._wh               = 0;         if (battler && battler.isEnemy()) {             const enemy = battler.enemy();             if (enemy && enemy.meta) {                 // <SVSheet>              — имя из Battle Graphic моба                 // <SVSheet: filename>    — имя из тега                 if (enemy.meta.SVSheet !== undefined) {                     this._isSVSheet = true;                     this.scale.x = -1; // враг смотрит влево                     const raw = String(enemy.meta.SVSheet).trim();                     if (raw.length > 0) this._svFileName = raw;                 }                 // <AttackRow: N>                 if (enemy.meta.AttackRow !== undefined) {                     const n = Number(String(enemy.meta.AttackRow).trim());                     if (!isNaN(n) && n >= 0 && n <= 5) this._attackRow = n;                 }                 // <SimpleSheet: i> или <SimpleSheet: i,row>                 if (enemy.meta.SimpleSheet !== undefined) {                     this._isSimpleSheet = true;                     const args = String(enemy.meta.SimpleSheet).split(',');                     if (args[0] && !isNaN(Number(args[0].trim())))                         this._simpleIndex = Number(args[0].trim());                     if (args[1] && !isNaN(Number(args[1].trim())))                         this._simpleRow = Number(args[1].trim());                 }                 // <Weapon: ID> или <Weapon: file,index>                 if (enemy.meta.Weapon && this._isSVSheet) {                     const args = enemy.meta.Weapon.split(',');                     if (args.length === 1 && !isNaN(Number(args[0].trim()))) {                         const wid = Number(args[0].trim());                         const weapon = $dataWeapons[wid];                         if (weapon) {                             this._weaponFile  = weapon.svWeapon || 'Weapons1';                             this._weaponIndex = Number(weapon.svWeaponIndex) || 0;                         }                     } else if (args.length >= 2) {                         this._weaponFile  = args[0].trim();                         this._weaponIndex = Number(args[1].trim()) || 0;                     }                 }             }         }         this.createCustomSprites();     };     // ============================================================     //  Создание тени и оружия     // ============================================================     Sprite_Enemy.prototype.createCustomSprites = function() {         // Тень         if (this._isSVSheet && !this._shadowSprite) {             const SW = 70, SH = 20;             this._shadowSprite = new Sprite(new Bitmap(SW, SH));             this._shadowSprite.anchor.x = 0.5;             this._shadowSprite.anchor.y = 0.5;             const ctx = this._shadowSprite.bitmap.context;             ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';             ctx.beginPath();             ctx.ellipse(SW/2, SH/2, SW/2 - 2, SH/2 - 2, 0, 0, Math.PI * 2);             ctx.fill();             this._shadowSprite.bitmap.baseTexture.update();         } else if (!this._isSVSheet && this._shadowSprite) {             if (this._shadowSprite.parent)                 this._shadowSprite.parent.removeChild(this._shadowSprite);             this._shadowSprite = null;         }         // Оружие (sibling, за спрайтом)         if (this._isSVSheet && this._weaponFile && !this._weaponSprite) {             const bmp = ImageManager.loadBitmap('img/system/', this._weaponFile, 0, true);             this._weaponSprite = new Sprite(bmp);             this._weaponSprite.anchor.x = 0.5;             this._weaponSprite.anchor.y = 0.5;             this._weaponSprite.visible = false;         } else if ((!this._isSVSheet || !this._weaponFile) && this._weaponSprite) {             if (this._weaponSprite.parent)                 this._weaponSprite.parent.removeChild(this._weaponSprite);             this._weaponSprite = null;         }     };     // ============================================================     //  updateBitmap     //  Имя файла: из тега <SVSheet: name>, иначе из Battle Graphic моба     // ============================================================     const _updateBitmap = Sprite_Enemy.prototype.updateBitmap;     Sprite_Enemy.prototype.updateBitmap = function() {         if (this._battler && this._battler.isEnemy()             && (this._isSVSheet || this._isSimpleSheet)) {             const name = this._svFileName || this._battler.battlerName();             if (this._bitmapName !== name) {                 this._bitmapName = name;                 this.bitmap = ImageManager.loadBitmap('img/sv_enemies/', name, 0, true);                 this._cw = 0;                 this._ch = 0;             }         } else {             _updateBitmap.call(this);         }     };     // ============================================================     //  updateFrame     // ============================================================     const _updateFrame = Sprite_Enemy.prototype.updateFrame;     Sprite_Enemy.prototype.updateFrame = function() {         if (this._isSVSheet) {             this.updateSVFrame();         } else if (this._isSimpleSheet) {             this.updateSimpleFrame();         } else {             _updateFrame.call(this);         }     };     // ============================================================     //  updatePosition — сдвиг спрайта     // ============================================================     const _updatePosition = Sprite_Enemy.prototype.updatePosition;     Sprite_Enemy.prototype.updatePosition = function() {         _updatePosition.call(this);         if (this._isSVSheet) {             this.x += SV_OFFSET_X;             this.y += SV_OFFSET_Y;         }     };     // ============================================================     //  update — тень и оружие как siblings     //  Порядок слоёв: [ ..., тень, оружие, спрайт ]     // ============================================================     const _update = Sprite_Enemy.prototype.update;     Sprite_Enemy.prototype.update = function() {         _update.call(this);         if (!this._isSVSheet || !this.parent) return;         const parent = this.parent;         // --- Тень ---         if (this._shadowSprite) {             if (this._shadowSprite.parent !== parent) {                 if (this._shadowSprite.parent)                     this._shadowSprite.parent.removeChild(this._shadowSprite);                 const idx = parent.getChildIndex(this);                 if (idx >= 0) parent.addChildAt(this._shadowSprite, idx);             }             this._shadowSprite.x       = this.x - SV_OFFSET_X;             this._shadowSprite.y       = this.y - SV_OFFSET_Y;             this._shadowSprite.visible = this.visible;             this._shadowSprite.opacity = this.opacity;         }         // --- Оружие ---         if (this._weaponSprite && this._weaponFile) {             if (this._weaponSprite.parent !== parent) {                 if (this._weaponSprite.parent)                     this._weaponSprite.parent.removeChild(this._weaponSprite);                 const idx = parent.getChildIndex(this);                 if (idx >= 0) parent.addChildAt(this._weaponSprite, idx);             }             const isPerforming = !!(this._battler && this._battler._performingAction);             this._weaponSprite.visible =                 this.visible && (WEAPON_SHOW_ALWAYS || isPerforming);             let ox = WEAPON_OFFSET_X;             const flipped = this.scale.x < 0;             if (flipped) ox = -ox;             this._weaponSprite.x = this.x + ox;             this._weaponSprite.y = this.y + WEAPON_OFFSET_Y;             this._weaponSprite.opacity = this.opacity;             this._weaponSprite.scale.x = WEAPON_SCALE * (flipped ? -1 : 1);             this._weaponSprite.scale.y = WEAPON_SCALE;         }     };     // ============================================================     //  SV-АНИМАЦИЯ ВРАГА     // ============================================================     Sprite_Enemy.prototype.updateSVFrame = function() {         if (!this.bitmap || !this.bitmap.isReady()) return;         if (this._cw === 0 && this.bitmap.width > 0) {             this._cw = Math.floor(this.bitmap.width / 9);             this._ch = Math.floor(this.bitmap.height / 6);         }         if (this._cw === 0 || this._ch === 0) return;         const battler = this._battler;         if (!battler) return;         const isPerforming = !!battler._performingAction;         // --- Атака ---         if (isPerforming && this._attackRow >= 0) {             if (this._attackStartFrame === null) {                 this._attackStartFrame = Graphics.frameCount;             }             const elapsed = Graphics.frameCount - this._attackStartFrame;             let phase = Math.floor(elapsed / SPEED_ATTACK);             if (phase > 2) phase = 2;             const col = 3 + phase;             const row = this._attackRow;             this.setFrame(col * this._cw, row * this._ch, this._cw, this._ch);             this.updateWeaponFrame(phase);             return;         }         // --- Idle ---         this._attackStartFrame = null;         this.updateWeaponFrame(-1);         const phase = Math.floor(Graphics.frameCount / SPEED_IDLE) % 3;         this.setFrame(phase * this._cw, 0, this._cw, this._ch);     };     // ============================================================     //  Кадр оружия     //  phase: 0,1,2 — фаза анимации; -1 — покой     // ============================================================     Sprite_Enemy.prototype.updateWeaponFrame = function(phase) {         if (!this._weaponSprite || !this._weaponSprite.bitmap) return;         const bmp = this._weaponSprite.bitmap;         if (!bmp.isReady() || !bmp.width) return;         if (this._ww === 0) {             this._ww = Math.floor(bmp.width  / WEAPON_TOTAL_COLS);             this._wh = Math.floor(bmp.height / WEAPON_TOTAL_ROWS);         }         if (this._ww === 0 || this._wh === 0) return;         const idx  = this._weaponIndex;         const row  = Math.floor(idx / 2);         const half = idx % 2;         const p    = (phase >= 0 && phase <= 2) ? phase : 0;         const col  = half * 3 + p;         this._weaponSprite.setFrame(             col * this._ww,             row * this._wh,             this._ww,             this._wh         );     };     // ============================================================     //  SimpleSheet     // ============================================================     Sprite_Enemy.prototype.updateSimpleFrame = function() {         if (!this.bitmap || !this.bitmap.isReady()) return;         if (this._cw === 0 && this.bitmap.width > 0) {             this._cw = Math.floor(this.bitmap.width / 12);             this._ch = Math.floor(this.bitmap.height / 2);         }         if (this._cw === 0) return;         const pattern = Math.floor(Graphics.frameCount / SPEED_IDLE) % 3;         const charIdx = this._simpleIndex - 1;         const row     = this._simpleRow - 1;         this.setFrame(             (charIdx * 3 + pattern) * this._cw,             row * this._ch,             this._cw,             this._ch         );     }; })();
Code:
/*: * @target MZ * @plugindesc Экипировка в бою: 1 слот = 1 ход, скрыта для мёртвых * @author * * @param GuardSkillId * @text ID скилла "Защита" * @type skill * @default 2 * @desc Скилл, который будет использован как "защита" при трате хода. *       По умолчанию ID 2 — стандартный Guard в MZ. * * @help * Добавляет команду "Экипировка" в боевое меню актёра. *  - Не показывается мёртвым актёрам. *  - За один ход можно сменить только ОДИН слот экипировки. *  - Optimize/Clear в бою заблокированы. *  - После смены слота сцена закрывается, ход тратится (Guard). *  - Если игрок ничего не поменял — ход не тратится. * * License: MIT */ (() => {     "use strict";     const PLUGIN_NAME    = "EquipInBattle";     const params         = PluginManager.parameters(PLUGIN_NAME);     const GUARD_SKILL_ID = Number(params["GuardSkillId"] || 2);     const COMMAND_EQUIP = "equip";     let pendingEquipReturn = false;     let inBattleEquip      = false;     let equipChanged       = false;     // ----- 1. Команда только для живых -----     const _makeCommandList = Window_ActorCommand.prototype.makeCommandList;     Window_ActorCommand.prototype.makeCommandList = function() {         _makeCommandList.call(this);         if (this._actor && !this._actor.isDead()) {             this.addCommand(TextManager.equip, COMMAND_EQUIP, true);         }     };     // ----- 2. В Scene_Equip из боя блокируем Optimize/Clear -----     const _equipCmdMakeList = Window_EquipCommand.prototype.makeCommandList;     Window_EquipCommand.prototype.makeCommandList = function() {         if (inBattleEquip) {             this.addCommand(TextManager.equip,    "equip",    true);             this.addCommand(TextManager.optimize, "optimize", false);             this.addCommand(TextManager.clear,    "clear",    false);         } else {             _equipCmdMakeList.call(this);         }     };     // ----- 3. Обработчик команды в боевом меню -----     const _createActorCommandWindow = Scene_Battle.prototype.createActorCommandWindow;     Scene_Battle.prototype.createActorCommandWindow = function() {         _createActorCommandWindow.call(this);         this._actorCommandWindow.setHandler(COMMAND_EQUIP, this.commandEquip.bind(this));     };     Scene_Battle.prototype.commandEquip = function() {         const actor = BattleManager.actor();         if (!actor || actor.isDead()) {             this.selectNextCommand();             return;         }         $gameParty.setMenuActor(actor);         pendingEquipReturn = true;         inBattleEquip      = true;         equipChanged       = false;         SceneManager.push(Scene_Equip);     };     // ----- 4. Одна смена слота = конец действия -----     const _onItemOk = Scene_Equip.prototype.onItemOk;     Scene_Equip.prototype.onItemOk = function() {         if (!inBattleEquip) {             _onItemOk.call(this);             return;         }         const slotId  = this._slotId;         const newItem = this._itemWindow.item();         const oldItem = this._actor.equips()[slotId];         _onItemOk.call(this);         if (oldItem !== newItem) {             equipChanged = true;             this.popScene();         }     };     // ----- 5. Возврат в бой -----     const _start = Scene_Battle.prototype.start;     Scene_Battle.prototype.start = function() {         if (pendingEquipReturn) {             pendingEquipReturn = false;             Scene_Base.prototype.start.call(this);             this.startFadeIn(this.fadeSpeed(), false);             this.onEquipReturn();         } else {             _start.call(this);         }     };     Scene_Battle.prototype.onEquipReturn = function() {         inBattleEquip = false;         if (equipChanged) {             this._actorCommandWindow.close();             this._statusWindow.hide();             // Тратим ход: актёр применит скилл Guard к своему последнему             // таргету (для Guard это не имеет значения, у него scope "None").             const actor = BattleManager.actor();             if (actor && typeof actor.forceAction === "function") {                 actor.forceAction(GUARD_SKILL_ID, -2);             }             this.selectNextCommand();         } else {             this._statusWindow.show();             this._actorCommandWindow.activate();         }     }; })();
Последнее редактирование: 2 нед. 1 день назад пользователем AnnTenna.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 5 дн. назад #132783 от Ryterator
Если я не так выложил можно сразу не банить первый раз выкладываю в остальном чаще смотрел..

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 5 дн. назад #132784 от Dassy
Ну.. дипсик точно ничего плохого не делает, кроме передачи всего и вся партии )

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 4 дн. назад #132788 от Ryterator
Залил сегодня обнову вроде бы всё работает если найду баги сам или кто подскажет залью опять с фиксами

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Ветеран Проект месяца 2 место Проект месяца 1 место Проект месяца 3 место Разработчик Поддержка Фонда
Больше
2 нед. 3 дн. назад #132793 от MarkerIl
На будущее, скрывай код плагина под спойлер. Читающим будет удобнее.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 3 дн. назад #132794 от Ryterator
Я пока не силён в форматировании. Сейчас попробуем...

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 3 дн. назад #132795 от Ryterator
Я не нашёл как скрывать.... Дайте ссылку на справку по форматированию текста.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Ветеран Проект месяца 2 место Проект месяца 1 место Проект месяца 3 место Разработчик Поддержка Фонда
Больше
2 нед. 3 дн. назад #132796 от MarkerIl
Под сообщением кнопка "Действие">"Изменить".

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 3 дн. назад #132797 от Ryterator
Не как редактировать а как вставить в текст....

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Ветеран Проект месяца 2 место Проект месяца 1 место Проект месяца 3 место Разработчик Поддержка Фонда
Больше
2 нед. 2 дн. назад #132798 от MarkerIl
ВНИМАНИЕ: Спойлер!
Спасибо сказали: Ryterator

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 2 дн. назад #132799 от Ryterator
Да я малоумный но я всё ещё не понял как, можно инструкцию сразу по шагам а не какие то намёки, или инструкцию к базовым действиям...

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 2 дн. назад #132800 от Ryterator
Всё допёр про что это.. Ума у меня мало

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 2 дн. назад #132803 от Ryterator
Я пол часа как бился с формированием, у меня не получается сунуть это в спойлер. Если у кого то выйдет я вставлю из цитаты

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Ветеран Проект месяца 2 место Проект месяца 1 место Проект месяца 3 место Разработчик Поддержка Фонда
Больше
2 нед. 8 ч. назад - 2 нед. 8 ч. назад #132806 от MarkerIl
ВНИМАНИЕ: Спойлер!

Часть почему-то зачеркивает. Остальные коды так же, просто скопируй и замени уже вставленный. Тут только первый из четырех.
Последнее редактирование: 2 нед. 8 ч. назад пользователем MarkerIl.
Спасибо сказали: Ryterator

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Больше
2 нед. 7 ч. назад #132807 от Ryterator
Лучше уж тогда длинный оставить чем сломанный его читать будет не удобно.

Пожалуйста Войти или Регистрация, чтобы присоединиться к беседе.

Время создания страницы: 0.103 секунд
Работает на Kunena форум