// DarkPlasma_Promise_BattleLayout 1.5.1
// Copyright (c) 2020 DarkPlasma

/**
 * 2021/08/26 1.5.1 モンスター図鑑を開いて閉じると行動選択中アクターのカーソルが消える不具合を修正
 * 2021/08/12 1.5.0 特定エネミーのHPゲージを非表示にする機能を追加
 * 2020/12/14 1.4.2 不要コードの削除
 * 2020/09/19 1.4.1 敵に対するカーソル画像の高さ計算を修正
 *            1.4.0 敵に対するカーソル画像の高さ計算を修正
 *                  敵に対するカーソル縦方向オフセット設定を追加
 * 2020/09/15 1.3.0 アクター背景、パーティ背景の設定追加
 * 2020/09/14 1.2.1 HP/MPゲージの色が暗くなっていた不具合修正
 * 2020/09/12 1.2.0 アクターに対するアニメーションの表示位置調整オプション追加
 *                  先頭の敵以外のHPゲージが正しく表示されない不具合修正
 * 2020/09/08 1.1.0 敵キャラHPゲージ表示を追加
 *            1.0.2 rollup構成へ移行
 * 2020/09/05 1.0.1 全体化カーソルに対応
 *            1.0.0 初回
 */

/*:ja
 * @plugindesc Promise用戦闘画面レイアウト調整
 * @author DarkPlasma
 * @license No License
 *
 * @target MZ
 *
 * @param actorFace
 * @text アクター顔グラ
 * @type struct<ActorFace>[]
 * @default []
 *
 * @param actorBackground
 * @text アクター背景画像(std_bg)
 * @type file
 * @dir img
 *
 * @param partyBackground
 * @text パーティ背景画像(hud_bg)
 * @type file
 * @dir img
 *
 * @param gaugeImage
 * @text ゲージ画像
 * @type file
 * @dir img
 *
 * @param gaugeBackgroundImage
 * @text ゲージ背景画像
 * @type file
 * @dir img
 *
 * @param hpLabelImage
 * @text HPラベル画像
 * @type file
 * @dir img
 *
 * @param mpLabelImage
 * @text MPラベル画像
 * @type file
 * @dir img
 *
 * @param numberImage
 * @text 数字画像
 * @type file
 * @dir img
 *
 * @param cursorImage
 * @text カーソル画像
 * @type file
 * @dir img
 *
 * @param hpDigits
 * @text HPの最大桁数
 * @type number
 * @default 5
 *
 * @param mpDigits
 * @text MPの最大桁数
 * @type number
 * @default 3
 *
 * @param enemyHpGaugeWidth
 * @text 敵HPゲージの長さ
 * @type number
 * @default 280
 *
 * @param enemiesHiddenHpGauge
 * @text HPゲージ非表示敵
 * @type enemy[]
 * @default []
 *
 * @param actorAnimationOffsetX
 * @desc アクターへのアニメーション表示位置を横方向に調整します。増やすと右へ、減らすと左へ動きます。
 * @text アニメーションオフセットX
 * @type number
 * @default 100
 *
 * @param actorAnimationOffsetY
 * @desc アクターへのアニメーション表示位置を縦方向に調整します。増やすと下へ、減らすと上へ動きます。
 * @text アニメーションオフセットY
 * @type number
 * @default 80
 *
 * @param cursorOffsetYForEnemy
 * @desc 敵へのカーソル表示位置を縦方向に調整します。増やすと上へ、減らすと下へ動きます。
 * @text 敵カーソルオフセットY
 * @type number
 * @default 60
 *
 * @help
 * version: 1.5.1
 * 戦闘画面のレイアウトをいい感じにします。
 */
/*~struct~ActorFace:
 * @param actorId
 * @text アクターID
 * @type actor
 *
 * @param image
 * @text 顔グラフィック画像
 * @type file
 * @dir img
 */
(() => {
  'use strict';

  const pluginName = document.currentScript.src.replace(/^.*\/(.*).js$/, function () {
    return arguments[1];
  });

  const pluginParameters = PluginManager.parameters(pluginName);

  const settings = {
    actorFace: JSON.parse(pluginParameters.actorFace || '[]').map((e) => {
      return ((parameter) => {
        const parsed = JSON.parse(parameter);
        return {
          actorId: Number(parsed.actorId || 0),
          image: String(parsed.image || ''),
        };
      })(e || '{}');
    }),
    actorBackground: String(pluginParameters.actorBackground || ''),
    partyBackground: String(pluginParameters.partyBackground || ''),
    gaugeImage: String(pluginParameters.gaugeImage || ''),
    gaugeBackgroundImage: String(pluginParameters.gaugeBackgroundImage || ''),
    hpLabelImage: String(pluginParameters.hpLabelImage || ''),
    mpLabelImage: String(pluginParameters.mpLabelImage || ''),
    numberImage: String(pluginParameters.numberImage || ''),
    cursorImage: String(pluginParameters.cursorImage || ''),
    hpDigits: Number(pluginParameters.hpDigits || 5),
    mpDigits: Number(pluginParameters.mpDigits || 3),
    enemyHpGaugeWidth: Number(pluginParameters.enemyHpGaugeWidth || 280),
    enemiesHiddenHpGauge: JSON.parse(pluginParameters.enemiesHiddenHpGauge || '[]').map((e) => {
      return Number(e || 0);
    }),
    actorAnimationOffsetX: Number(pluginParameters.actorAnimationOffsetX || 100),
    actorAnimationOffsetY: Number(pluginParameters.actorAnimationOffsetY || 80),
    cursorOffsetYForEnemy: Number(pluginParameters.cursorOffsetYForEnemy || 60),
  };

  const CURSOR_TARGET_TYPE = {
    ACTOR: 'actor',
    ENEMY: 'enemy',
  };

  const ACTOR_EFFECT_TYPE = {
    FLASH: 'flash',
  };

  const ENEMY_GAUGE_HEIGHT = 12;

  const _Game_Actor_performActionStart = Game_Actor.prototype.performActionStart;
  Game_Actor.prototype.performActionStart = function (action) {
    _Game_Actor_performActionStart.call(this, action);
    this.requestEffect(ACTOR_EFFECT_TYPE.FLASH);
  };

  /**
   * @return {boolean}
   */
  Game_Actor.prototype.isFlashEffectRequested = function () {
    return this._effectType === ACTOR_EFFECT_TYPE.FLASH;
  };

  /**
   * サイドビューではないが、顔グラをスプライト扱いする
   * @return {boolean}
   */
  Game_Actor.prototype.isSpriteVisible = function () {
    return true;
  };

  /**
   * HPゲージを表示すべきかどうか
   * @return {boolean}
   */
  Game_Enemy.prototype.mustBeShownHpGauge = function () {
    return !settings.enemiesHiddenHpGauge.includes(this.enemyId());
  };

  const _Scene_Battle_createDisplayObjects = Scene_Battle.prototype.createDisplayObjects;
  Scene_Battle.prototype.createDisplayObjects = function () {
    _Scene_Battle_createDisplayObjects.call(this);
    this.createEnemyNameWindow();
    this.createCursorSprites();
    this._statusWindow.setActorBackgroundSprites(this._spriteset.actorBackgroundSprites());
  };

  /**
   * カーソル画像を生成する
   */
  Scene_Battle.prototype.createCursorSprites = function () {
    /**
     * 全体化時用に複数作る？
     * 敵が増えるとややこしいので、ここでは一つだけでも良いかも
     */
    this._cursorSprites = Array.from(
      Array(Math.max($gameParty.battleMembers().length, $gameTroop.members().length)).keys()
    ).map((_) => new Sprite_BattleCursor());
    this._cursorSprites.forEach((sprite) => {
      sprite.visible = false;
      this.addChild(sprite);
    });
  };

  Scene_Battle.prototype.createEnemyNameWindow = function () {
    this._enemyInfoWindow = new Window_BattleEnemyInfo(this.enemyNameWindowRect());
    this._enemyInfoWindow.hide();
    this.addChild(this._enemyInfoWindow);
    this._enemyWindow.setHelpWindow(this._enemyInfoWindow);
  };

  const _Scene_Battle_createActorCommandWindow = Scene_Battle.prototype.createActorCommandWindow;
  Scene_Battle.prototype.createActorCommandWindow = function () {
    _Scene_Battle_createActorCommandWindow.call(this);
    /**
     * 何故か画面最下部に固定しようとしてくるので、強引に書き換える
     */
    this._actorCommandWindow.y = 0;
  };

  /**
   * エネミーウィンドウの範囲
   * この範囲に重なると後ろのオブジェクトが非表示になるため、大きさ0にする
   * @return {Rectangle}
   */
  Scene_Battle.prototype.enemyWindowRect = function () {
    return new Rectangle(0, 0, 0, 0);
  };

  Scene_Battle.prototype.enemyNameWindowRect = function () {
    return new Rectangle(0, 0, Graphics.width, this.calcWindowHeight(1, false));
  };

  Scene_Battle.prototype.partyCommandWindowRect = function () {
    const ww = Graphics.boxWidth;
    const wh = this.calcWindowHeight(1, true);
    const wx = 0;
    const wy = 0;
    return new Rectangle(wx, wy, ww, wh);
  };

  Scene_Battle.prototype.actorCommandWindowRect = function () {
    return this.partyCommandWindowRect();
  };

  Scene_Battle.prototype.skillWindowRect = function () {
    const ww = Graphics.boxWidth;
    const wh = this.calcWindowHeight(5, true);
    const wx = 0;
    const wy = this.helpAreaHeight();
    return new Rectangle(wx, wy, ww, wh);
  };

  Scene_Battle.prototype.statusWindowRect = function () {
    const extra = 10;
    const ww = Graphics.boxWidth;
    const wh = 150;
    const wx = this.isRightInputMode() ? 0 : Graphics.boxWidth - ww;
    const wy = Graphics.boxHeight - wh + extra - 4;
    return new Rectangle(wx, wy, ww, wh);
  };

  const _Scene_Battle_startActorCommandSelection = Scene_Battle.prototype.startActorCommandSelection;
  Scene_Battle.prototype.startActorCommandSelection = function () {
    _Scene_Battle_startActorCommandSelection.call(this);
    this._cursorSprites[0].show();
    this._cursorSprites.slice(1).forEach((sprite) => sprite.hide());
    this.updateCursor();
  };

  const _Scene_Battle_startActorSelection = Scene_Battle.prototype.startActorSelection;
  Scene_Battle.prototype.startActorSelection = function () {
    _Scene_Battle_startActorSelection.call(this);
    this._cursorSprites[0].show();
    this._cursorSprites.slice(1).forEach((sprite) => sprite.hide());
    this.updateCursor();
  };

  const _Scene_Battle_startEnemySelection = Scene_Battle.prototype.startEnemySelection;
  Scene_Battle.prototype.startEnemySelection = function () {
    _Scene_Battle_startEnemySelection.call(this);
    /**
     * 敵選択中はアクターのコマンド系ウィンドウを隠しておく
     */
    this._actorCommandWindow.hide();
    this._skillWindow.hide();
    this._itemWindow.hide();
    this._enemyInfoWindow.show();
    this._statusWindow.show();
    this.updateCursor();
  };

  const _Scene_Battle_commandSkill = Scene_Battle.prototype.commandSkill;
  Scene_Battle.prototype.commandSkill = function () {
    _Scene_Battle_commandSkill.call(this);
    /**
     * 強引に再表示
     */
    this._statusWindow.show();
  };

  const _Scene_Battle_commandItem = Scene_Battle.prototype.commandItem;
  Scene_Battle.prototype.commandItem = function () {
    _Scene_Battle_commandItem.call(this);
    /**
     * 強引に再表示
     */
    this._statusWindow.show();
  };

  const _Scene_Battle_onEnemyOk = Scene_Battle.prototype.onEnemyOk;
  Scene_Battle.prototype.onEnemyOk = function () {
    _Scene_Battle_onEnemyOk.call(this);
    this._enemyInfoWindow.hide();
  };

  const _Scene_Battle_onEnemyCancel = Scene_Battle.prototype.onEnemyCancel;
  Scene_Battle.prototype.onEnemyCancel = function () {
    _Scene_Battle_onEnemyCancel.call(this);
    this._enemyInfoWindow.hide();
    if (this._actorCommandWindow.currentSymbol() === 'attack') {
      this._actorCommandWindow.show();
    }
  };

  Scene_Battle.prototype.statusWindowX = function () {
    return this.statusWindowRect().x;
  };

  const _Scene_Battle_update = Scene_Battle.prototype.update;
  Scene_Battle.prototype.update = function () {
    _Scene_Battle_update.call(this);
    this.updateCursor();
    this.updateActorSprites();
  };

  Scene_Battle.prototype.updateActorSprites = function () {
    this._statusWindow.setActorSprites(this._spriteset.actorSprites());
  };

  Scene_Battle.prototype.updateCursor = function () {
    if (
      (!this._partyCommandWindow.active && this._actorCommandWindow.active) ||
      this._skillWindow.active ||
      this._itemWindow.active
    ) {
      this._cursorSprites[0].setTarget(CURSOR_TARGET_TYPE.ACTOR, this._statusWindow.index());
      this._cursorSprites[0].show();
      this._cursorSprites.slice(1).forEach((sprite) => sprite.hide());
    } else if (this._actorWindow.active) {
      if (this._actorWindow.cursorAll()) {
        $gameParty.battleMembers().forEach((_, index) => {
          this._cursorSprites[index].setTarget(CURSOR_TARGET_TYPE.ACTOR, index);
          this._cursorSprites[index].show();
        });
      } else {
        this._cursorSprites[0].setTarget(CURSOR_TARGET_TYPE.ACTOR, this._actorWindow.index());
        this._cursorSprites.slice(1).forEach((sprite) => sprite.hide());
      }
    } else if (this._enemyWindow.active) {
      /**
       * 敵画像ローディングの関係で、ここで初回設定を行う
       */
      if (!this._cursorSprites[0].enemyHeight(0)) {
        const enemyHeights = this._spriteset.enemyHeights();
        this._cursorSprites.forEach((sprite) => sprite.setEnemyHeights(enemyHeights));
      }
      if (this._enemyWindow.cursorAll()) {
        $gameTroop.aliveMembers().forEach((enemy) => {
          const index = enemy.index();
          this._cursorSprites[index].setTarget(CURSOR_TARGET_TYPE.ENEMY, index);
          this._cursorSprites[index].show();
        });
      } else {
        this._cursorSprites[0].setTarget(CURSOR_TARGET_TYPE.ENEMY, this._enemyWindow.enemyIndex());
        this._cursorSprites.slice(1).forEach((sprite) => sprite.hide());
      }
    } else {
      this._cursorSprites.forEach((sprite) => sprite.hide());
    }
  };

  Spriteset_Battle.prototype.createActors = function () {
    this._partyBackgroundSprite = new Sprite(ImageManager.loadPartyBackground());
    this._partyBackgroundSprite.move(-5, Graphics.height - 160);
    this._battleField.addChild(this._partyBackgroundSprite);
    this._actorBackgroundSprites = $gameParty.battleMembers().map((_) => new Sprite_BattleActorBackground());
    this._actorBackgroundSprites.forEach((sprite) => {
      sprite.hide();
      this._battleField.addChild(sprite);
    });
    this._actorSprites = $gameParty.battleMembers().map((actor) => {
      return new Sprite_BattleActor(actor);
    });
    this._actorSprites.forEach((sprite) => {
      sprite.visible = false;
      this._battleField.addChild(sprite);
    });
  };

  /**
   * @return {Sprite_BattleActorBackground[]}
   */
  Spriteset_Battle.prototype.actorBackgroundSprites = function () {
    return this._actorBackgroundSprites;
  };

  /**
   * @return {Sprite_BattleActor[]}
   */
  Spriteset_Battle.prototype.actorSprites = function () {
    return this._actorSprites;
  };

  /**
   * エネミー画像の高さリスト
   * @return {number[]}
   */
  Spriteset_Battle.prototype.enemyHeights = function () {
    return this._enemySprites
      .map((sprite) => [sprite.bitmap.height, sprite._enemy])
      .sort(([_, enemy1], [__, enemy2]) => {
        return $gameTroop.members().indexOf(enemy1) - $gameTroop.members().indexOf(enemy2);
      })
      .map(([height, _]) => height);
  };

  const _Sprite_Animation_targetSpritePosition = Sprite_Animation.prototype.targetSpritePosition;
  Sprite_Animation.prototype.targetSpritePosition = function (sprite) {
    const result = _Sprite_Animation_targetSpritePosition.call(this, sprite);
    if ($gameParty.inBattle() && sprite.isActor()) {
      result.x += settings.actorAnimationOffsetX;
      result.y += settings.actorAnimationOffsetY;
    }
    return result;
  };

  const _Sprite_AnimationMV_updatePosition = Sprite_AnimationMV.prototype.updatePosition;
  Sprite_AnimationMV.prototype.updatePosition = function () {
    _Sprite_AnimationMV_updatePosition.call(this);
    if (
      $gameParty.inBattle() &&
      this._animation.position !== 3 &&
      this._targets.length > 0 &&
      this._targets[0].isActor()
    ) {
      this.x += settings.actorAnimationOffsetX;
      this.y += settings.actorAnimationOffsetY;
    }
  };

  Sprite_Battler.prototype.isActor = function () {
    return false;
  };

  /**
   * アクターの顔グラ
   */
  class Sprite_BattleActor extends Sprite_Battler {
    initMembers() {
      super.initMembers();
      this.anchor.x = 0;
      this.anchor.y = 0;
    }

    setBattler(actor) {
      super.setBattler(actor);
      this.loadBitmap();
    }

    isActor() {
      return true;
    }

    actor() {
      return this._battler;
    }

    loadBitmap() {
      this.bitmap = ImageManager.loadBattlerFace(this._battler.actorId());
    }

    setupFlashEffect() {
      this._flashColor = [255, 255, 255, 160];
      this._flashDuration = 16;
    }

    damageOffsetX() {
      return super.damageOffsetX() + 80;
    }

    damageOffsetY() {
      return super.damageOffsetY() + 30;
    }

    update() {
      super.update();
      this.updateEffect();
      this.updateFlash();
    }

    updateEffect() {
      if (this.actor() && this.actor().isFlashEffectRequested()) {
        this.setupFlashEffect();
        this.actor().clearEffect();
      }
    }

    updateFlash() {
      if (this._flashDuration > 0) {
        this._flashDuration--;
        this._flashColor[3] = 128 - (16 - this._flashDuration) * 10;
        this.setBlendColor(this._flashColor);
      }
    }
  }

  class Sprite_BattleActorBackground extends Sprite {
    initialize() {
      super.initialize();
      this.loadBitmap();
    }

    loadBitmap() {
      this.bitmap = ImageManager.loadBattleActorBackground();
    }
  }

  /**
   * HP/MPゲージと数字、HP/MPラベル
   */
  class Sprite_BattleGauge extends Sprite_Gauge {
    createBitmap() {
      this._backgroundSprite = new Sprite(ImageManager.loadBattleGaugeBackground());
      this.addChild(this._backgroundSprite);
      this._mainSprite = new Sprite_BattleGaugeMain();
      this.addChild(this._mainSprite);
    }

    setup(battler, statusType) {
      super.setup(battler, statusType);
      if (!this._labelSprite) {
        this._labelSprite = new Sprite(
          statusType === 'hp' ? ImageManager.loadBattleHpLabel() : ImageManager.loadBattleMpLabel()
        );
        this._labelSprite.move(60, 0);
        this.addChild(this._labelSprite);
      }
      if (!this._numberSprite) {
        this._numberSprite =
          statusType === 'hp'
            ? new Sprite_BattleHpNumber(this.currentValue(), this.currentMaxValue())
            : new Sprite_BattleMpNumber(this.currentValue(), this.currentMaxValue());
        this.addChild(this._numberSprite);
      }
    }

    destroy(options) {
      Sprite.prototype.destroy.call(this, options);
    }

    redraw() {
      const currentValue = this.currentValue();
      if (!isNaN(currentValue)) {
        this.drawGauge();
        if (this._statusType !== 'time') {
          this.drawLabel();
          if (this.isValid()) {
            this.drawValue();
          }
        }
      }
    }

    /**
     * @return {number}
     */
    gaugeX() {
      return 60;
    }

    /**
     * @param {number} x X座標
     * @param {number} y Y座標
     * @param {number} width 幅（未使用）
     * @param {number} height 高さ（未使用）
     */
    drawGaugeRect(x, y, width, height) {
      this._mainSprite.move(x + 3, y + 2);
      this._backgroundSprite.move(x, y);
      const frameY = this._statusType === 'hp' ? 0 : 8;
      this._mainSprite.setFrame(0, frameY, this._mainSprite.fullWidth() * this.gaugeRate(), 7);
    }

    drawLabel() {}

    drawValue() {
      if (this._numberSprite) {
        this._numberSprite.setValue(this.currentValue());
        this._numberSprite.setMaxValue(this.currentMaxValue());
        this._numberSprite.refresh();
      }
    }
  }

  /**
   * HP/MPゲージのメイン部
   */
  class Sprite_BattleGaugeMain extends Sprite {
    initialize() {
      super.initialize();
      this.loadBitmap();
    }

    loadBitmap() {
      this.bitmap = ImageManager.loadBattleGauge();
    }

    fullWidth() {
      return this.bitmap.width;
    }
  }

  /**
   * HP/MPの数値
   */
  class Sprite_BattleNumber extends Sprite {
    initialize(value, maxValue) {
      super.initialize();
      this._value = value;
      this._maxValue = maxValue;
      this.setFrame(0, 0, Graphics.width, Graphics.height);
      this._sprites = Array.from(Array(this.maxDigits()).keys()).map((_) => {
        const sprite = new Sprite();
        sprite.bitmap = this.numberImageBitmap();
        sprite.x = this.baseX();
        sprite.y = this.y();
        sprite.anchor.x = 0.5;
        sprite.anchor.y = 0.5;
        sprite.visible = false;
        return sprite;
      });
      this._sprites.forEach((sprite) => this.addChild(sprite));
      this._changed = true;
    }

    get sprites() {
      return this._sprites;
    }

    /**
     * @return {number} 基準X座標
     */
    baseX() {
      return 176;
    }

    /**
     * @return {number} Y座標
     */
    y() {
      return 0;
    }

    /**
     * @return {number} 最大桁数
     */
    maxDigits() {
      return 1;
    }

    /**
     * @return {Bitmap} 数値画像ビットマップ
     */
    numberImageBitmap() {
      return ImageManager.loadBattleNumber();
    }

    /**
     * @return {number} 数字画像1桁の幅
     */
    numberImageWidth() {
      return Math.round(this.sprites[0].bitmap.width / 10);
    }

    /**
     * @return {number} 数字画像1桁の高さ
     */
    numberImageHeight() {
      return Math.round(this.sprites[0].bitmap.height / 3);
    }

    /**
     * @return {number} 描画する数値
     */
    number() {
      return this._value || 0;
    }

    /**
     * @param {number} value 描画する数値
     */
    setValue(value) {
      this._value = value;
    }

    /**
     * @param {number} maxValue 最大値
     */
    setMaxValue(maxValue) {
      this._maxValue = maxValue;
    }

    /**
     * @return {string[]} 描画する数値を1桁ずつ文字列に分解した配列
     */
    numberStringArray() {
      return Math.abs(this.number()).toString().split('');
    }

    /**
     * 並び替えで変化した時に呼ぶ
     */
    change() {
      this._changed = true;
    }

    update() {
      if (this._changed) {
        this.refresh();
        this._changed = false;
      }
    }

    numberFrameY() {
      if (this._value === 0) {
        return Math.round(this.numberImageHeight() * 2);
      }
      /**
       * 25％以下で瀕死表示。TODO: 設定可能にする
       */
      if (this._maxValue && this._value < this._maxValue * 0.25) {
        return Math.round(this.numberImageHeight());
      }
      return 0;
    }

    refresh() {
      /**
       * まず全spriteを非表示にする
       */
      this._sprites.forEach((sprite) => {
        sprite.visible = false;
      });
      const numberOfDigits = this.numberStringArray().length;
      /**
       * 1桁ずつSpriteを用意する
       */
      this.numberStringArray().forEach((numberString, index) => {
        const number = Number(numberString);
        this.sprites[index].setFrame(
          number * this.numberImageWidth(),
          this.numberFrameY(),
          this.numberImageWidth(),
          this.numberImageHeight()
        );
        /**
         * 数字と数字の間はちょっとだけ詰める（-5）
         */
        this.sprites[index].x = this.baseX() - (this.numberImageWidth() - 5) * (numberOfDigits - index);
        this.sprites[index].visible = true;
      });
    }
  }

  class Sprite_BattleHpNumber extends Sprite_BattleNumber {
    maxDigits() {
      return settings.hpDigits;
    }
  }

  class Sprite_BattleMpNumber extends Sprite_BattleNumber {
    maxDigits() {
      return settings.mpDigits;
    }
  }

  /**
   * ターゲットカーソル画像
   */
  class Sprite_BattleCursor extends Sprite {
    initialize() {
      super.initialize();
      this.loadBitmap();
      this._animationCount = 0;
      this._targetType = CURSOR_TARGET_TYPE.ACTOR;
      this._targetIndex = 0;
      this._enemyHeights = [];
    }

    loadBitmap() {
      this.bitmap = ImageManager.loadBattleCursor();
    }

    /**
     * @param {number[]} enemyHeights エネミー画像の高さリスト
     */
    setEnemyHeights(enemyHeights) {
      this._enemyHeights = enemyHeights;
    }

    /**
     * エネミー画像の高さ
     * @param {number} index インデックス
     * @return {number}
     */
    enemyHeight(index) {
      return this._enemyHeights.length > index ? this._enemyHeights[index] : 0;
    }

    /**
     * @param {string} type ターゲット種別
     * @param {number} index ターゲットインデックス
     */
    setTarget(type, index) {
      this._targetType = type;
      this._targetIndex = index;
    }

    animationSpeed() {
      return 6;
    }

    animationFrame() {
      return Math.floor(this._animationCount / this.animationSpeed());
    }

    update() {
      this._animationCount++;
      if (this.animationFrame() > 5) {
        this._animationCount = 0;
      }
      this.updatePosition();
      this.setFrame(56 * this.animationFrame(), 0, 56, this.bitmap.height);
    }

    updatePosition() {
      if (this._targetType === CURSOR_TARGET_TYPE.ACTOR) {
        this.move((Graphics.boxWidth / 4) * this._targetIndex + 50, 400);
      } else {
        const targetEnemy = $gameTroop.members()[this._targetIndex];
        this.move(
          targetEnemy.screenX() - 20,
          targetEnemy.screenY() - Math.floor(this.enemyHeight(this._targetIndex) * 0.9) - settings.cursorOffsetYForEnemy
        );
      }
    }
  }

  class Sprite_EnemyGauge extends Sprite_Gauge {
    label() {
      return '';
    }

    bitmapWidth() {
      return settings.enemyHpGaugeWidth;
    }

    gaugeHeight() {
      return ENEMY_GAUGE_HEIGHT;
    }

    drawLabel() {}
    drawValue() {}
  }

  Window_BattleStatus.prototype.faceRect = function (index) {
    const rect = this.itemRect(index);
    rect.pad(-1);
    return rect;
  };

  /**
   * @param {Sprite_BattleActorBackground[]} sprites アクター背景画像
   */
  Window_BattleStatus.prototype.setActorBackgroundSprites = function (sprites) {
    sprites.forEach((sprite, index) => {
      const key = `actor-background${index}`;
      this._additionalSprites[key] = sprite;
    });
  };

  /**
   * アクターの顔グラをセットする
   * 元々はクラス内で生成・管理していたが、バトラー画像としても扱う関係上、
   * 管理をspriteset側に移し、こちらはそれを受け取るだけとする
   * @param {Sprite_BattleActor[]} actorSprites アクター画像リスト
   */
  Window_BattleStatus.prototype.setActorSprites = function (actorSprites) {
    actorSprites.map((sprite) => {
      const key = `actor${sprite.actor().actorId()}-face`;
      this._additionalSprites[key] = sprite;
    });
    this.refresh();
  };

  Window_BattleStatus.prototype.innerSprite = function (key) {
    return this._additionalSprites[key];
  };

  Window_BattleStatus.prototype.drawItemImage = function (index) {
    const actor = this.actor(index);
    const rect = this.faceRect(index);
    this.drawActorFace(actor, rect.x, rect.y, rect.width, rect.height);
  };

  // prettier-ignore
  Window_BattleStatus.prototype.drawActorFace = function (
    actor, x, y, width, height
  ) {
    const key = "actor%1-face".format(actor.actorId());
    const sprite = this.innerSprite(key);
    if (sprite && this.isOpen()) {
      sprite.setHome(this.innerRect.x + x, this.y + this.innerRect.y + y + 28);
      sprite.show();
    }
  };

  Window_BattleStatus.prototype.drawItemStatus = function (index) {
    const actor = this.actor(index);
    const rect = this.itemRectWithPadding(index);
    const stateIconX = this.stateIconX(rect);
    const stateIconY = this.stateIconY(rect);
    const basicGaugesX = this.basicGaugesX(rect);
    const basicGaugesY = this.basicGaugesY(rect);
    this.placeStateIcon(actor, stateIconX, stateIconY);
    this.placeBasicGauges(actor, basicGaugesX, basicGaugesY);
  };

  /**
   * @param {number} index インデックス
   */
  Window_BattleStatus.prototype.drawItemBackground = function (index) {
    const key = 'actor-background%1'.format(index);
    const sprite = this.innerSprite(key);
    if (sprite && this.isOpen()) {
      const faceRect = this.faceRect(index);
      sprite.move(faceRect.x + this.innerRect.x - 16, faceRect.y + this.y + this.innerRect.y - 16);
      sprite.show();
    }
  };

  Window_BattleStatus.prototype.updateBackOpacity = function () {
    this.backOpacity = 0;
  };

  /**
   * @param {Rectangle} rect アクターの範囲
   * @return {number}
   */
  Window_BattleStatus.prototype.basicGaugesY = function (rect) {
    const bottom = rect.y + rect.height - this.extraHeight();
    const numGauges = $dataSystem.optDisplayTp ? 3 : 2;
    return bottom - this.gaugeLineHeight() * numGauges + 16;
  };

  Window_BattleStatus.prototype.gaugeLineHeight = function () {
    return 44;
  };

  Window_BattleStatus.prototype.placeBasicGauges = function (actor, x, y) {
    this.placeGauge(actor, 'hp', x, y);
    this.placeGauge(actor, 'mp', x, y + this.gaugeLineHeight());
    if ($dataSystem.optDisplayTp) {
      this.placeGauge(actor, 'tp', x, y + this.gaugeLineHeight() * 2);
    }
  };

  Window_BattleStatus.prototype.placeGauge = function (actor, type, x, y) {
    const key = 'actor%1-gauge-%2'.format(actor.actorId(), type);
    const sprite = this.createInnerSprite(key, Sprite_BattleGauge);
    sprite.setup(actor, type);
    sprite.move(x, y);
    sprite.show();
  };

  /**
   * カーソル範囲描画は不要
   */
  Window_BattleStatus.prototype.refreshCursor = function () {};

  /**
   * パーティが入れ替わった時用リフレッシュ
   */
  Window_BattleStatus.prototype.preparePartyRefresh = function () {
    $gameTemp.clearBattleRefreshRequest();
    for (const actor of $gameParty.members()) {
      const bitmap = ImageManager.loadBattlerFace(actor.actorId());
      bitmap.addLoadListener(this.performPartyRefresh.bind(this));
    }
  };

  const _Window_BattleEnemy_initialize = Window_BattleEnemy.prototype.initialize;
  Window_BattleEnemy.prototype.initialize = function (rect) {
    _Window_BattleEnemy_initialize.call(this, rect);
    this.setBackgroundType(2);
  };

  /**
   * 何も表示する必要がない
   */
  Window_BattleEnemy.prototype.paint = function () {};
  Window_BattleEnemy.prototype.refreshCursor = function () {};

  /**
   * 名前ウィンドウをヘルプとしてセットする
   */
  const _Window_BattleEnemy_updateHelp = Window_BattleEnemy.prototype.updateHelp;
  Window_BattleEnemy.prototype.updateHelp = function () {
    _Window_BattleEnemy_updateHelp.call(this);
    if (this.enemy() && this._helpWindow) {
      this._helpWindow.setEnemy(this.enemy());
    }
  };

  /**
   * 敵名前ウィンドウ
   */
  class Window_BattleEnemyInfo extends Window_StatusBase {
    initialize(rect) {
      super.initialize(rect);
      this.downArrowVisible = false;
    }

    maxScrollY() {
      return 0;
    }

    clear() {
      this.setEnemy(null);
    }

    baseTextRect() {
      const textWidth = this.textWidth(this._enemy.name());
      return new Rectangle(this.innerWidth / 2 - textWidth / 2, 0, textWidth, this.innerHeight);
    }

    gaugeWidth() {
      return settings.enemyHpGaugeWidth;
    }

    gaugeHeight() {
      return ENEMY_GAUGE_HEIGHT;
    }

    /**
     * @param {Game_Enemy|null} enemy 敵キャラ情報
     */
    setEnemy(enemy) {
      if (this._enemy !== enemy) {
        this._enemy = enemy;
        this.refresh();
      }
    }

    /**
     * @return {Game_Enemy|null}
     */
    enemy() {
      return this._enemy;
    }

    addInnerChild(child) {
      this._innerChildren.push(child);
      return this.addChildToBack(child);
    }

    /**
     * エネミーのHPゲージを表示する
     * @param {Game_Enemy} enemy 敵キャラ情報
     * @param {string} type ゲージ種別(hpが渡される)
     * @param {number} x X座標
     * @param {number} y Y座標
     */
    placeGauge(enemy, type, x, y) {
      const key = 'enemy%1-gauge-%2'.format(enemy.name(), type);
      const sprite = this.createInnerSprite(key, Sprite_EnemyGauge);
      sprite.setup(enemy, type);
      sprite.move(x, y);
      if (enemy.mustBeShownHpGauge()) {
        sprite.show();
      } else {
        sprite.hide();
      }
      /**
       * 他のスプライトを非表示にする
       */
      Object.entries(this._additionalSprites)
        .filter(([spriteKey, _]) => spriteKey !== key)
        .forEach(([_, sprite]) => sprite.hide());
    }

    refresh() {
      this.contents.clear();
      if (this.enemy()) {
        const rect = this.baseTextRect();
        this.placeGauge(
          this.enemy(),
          'hp',
          this.innerWidth / 2 - this.gaugeWidth() / 2,
          rect.height - ENEMY_GAUGE_HEIGHT
        );
        this.drawTextEx(this.enemy().name(), rect.x, rect.y, rect.width);
      }
    }
  }

  /**
   * 横倒し
   */
  Window_PartyCommand.prototype.maxCols = function () {
    return 3;
  };

  Window_ActorCommand.prototype.maxCols = function () {
    return 4;
  };

  /**
   * @param {number} actorId アクターID
   * @return {Bitmap}
   */
  ImageManager.loadBattlerFace = function (actorId) {
    const faceSetting = settings.actorFace.find((face) => face.actorId === actorId);
    return faceSetting ? this.loadBitmap('img/', faceSetting.image) : this._emptyBitmap;
  };

  ImageManager.loadBattleActorBackground = function () {
    return this.loadBitmap('img/', settings.actorBackground);
  };

  ImageManager.loadPartyBackground = function () {
    return this.loadBitmap('img/', settings.partyBackground);
  };

  ImageManager.loadBattleGauge = function () {
    return this.loadBitmap('img/', settings.gaugeImage);
  };

  ImageManager.loadBattleGaugeBackground = function () {
    return this.loadBitmap('img/', settings.gaugeBackgroundImage);
  };

  ImageManager.loadBattleHpLabel = function () {
    return this.loadBitmap('img/', settings.hpLabelImage);
  };

  ImageManager.loadBattleMpLabel = function () {
    return this.loadBitmap('img/', settings.mpLabelImage);
  };

  ImageManager.loadBattleNumber = function () {
    return this.loadBitmap('img/', settings.numberImage);
  };

  ImageManager.loadBattleCursor = function () {
    return this.loadBitmap('img/', settings.cursorImage);
  };
})();
