// DarkPlasma_Promise_Teleport 1.1.0
// Copyright (c) 2021 DarkPlasma

/**
 * 2021/03/07 1.1.0 テレポート先選択とテレポート処理を分離
 *                  テレポート先選択後に実行するコモンイベント設定を追加
 * 2021/03/06 1.0.0 初版
 */

/*:ja
 * @plugindesc Promise用のテレポートを実現する
 * @author DarkPlasma
 * @license No License
 *
 * @target MZ
 *
 * @param maxRegisterCount
 * @desc テレポート先の登録数を指定します。0で無限
 * @text 登録数
 * @type number
 * @default 5
 *
 * @param fixedPoints
 * @desc 登録せずにテレポートできる場所を指定します。
 * @text 固定テレポ先
 * @type struct<FixedPoint>[]
 * @default []
 *
 * @param commonEvent
 * @desc テレポート先選択後に実行するコモンイベントを選択します。
 * @text 選択後コモンイベント
 * @type common_event
 * @default 0
 *
 * @command reserveTeleport
 * @text テレポート先を選択する
 * @desc テレポート先を選択します。選択した後、テレポートするプラグインコマンドで実際にテレポートできます。
 *
 * @command teleport
 * @text テレポートする
 * @desc 選択したテレポート先へテレポートします。
 *
 * @command registerPoint
 * @text テレポート先を登録する
 * @desc テレポート先をプレイヤーが今いる座標に登録します。
 * @arg undefined
 * @text 名前
 * @desc テレポート先の名前。空欄にするとマップの表示名になります。
 * @type string
 *
 * @help
 * version: 1.1.0
 * Promise用にテレポートを実現します。
 * テレポート登録方法
 * テレポートを登録したい場所で、プラグインコマンド「テレポート先を登録する」を実行。
 *
 * テレポート方法1（メニューからテレポート実行）
 * 1. DarkPlasma_MenuLayoutのその他シーンに Scene_Teleport を指定。
 * 2. 本プラグインの選択後コモンイベントにアニメーションやウェイト、スイッチ操作などを書く。
 * 3. 2のコモンイベントでプラグインコマンド「テレポートする」を実行。
 *
 * テレポート方法2（イベントからテレポート実行）
 * 1. イベント内でプラグインコマンド「テレポート先を選択する」を実行。
 * 2. 同イベント内でアニメーションやウェイト、スイッチ操作などを書く。
 * 3. 同イベント内でプラグインコマンド「テレポートする」を実行。
 */
/*~struct~FixedPoint:
 * @param mapId
 * @desc マップID
 * @text マップID
 * @type number
 * @default 1
 *
 * @param name
 * @desc テレポート先の名前
 * @text 名前
 * @type string
 *
 * @param x
 * @desc X座標
 * @text X座標
 * @type number
 * @default 0
 *
 * @param y
 * @desc Y座標
 * @text Y座標
 * @type number
 * @default 0
 *
 * @param switchId
 * @desc このスイッチが有効な場合のみ、この座標へテレポート可能になります。0で条件なし。
 * @text スイッチ
 * @type switch
 * @default 0
 */
(() => {
  'use strict';

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

  const pluginParameters = PluginManager.parameters(pluginName);

  const settings = {
    maxRegisterCount: Number(pluginParameters.maxRegisterCount || 5),
    fixedPoints: JSON.parse(pluginParameters.fixedPoints || '[]').map((e) => {
      return ((parameter) => {
        const parsed = JSON.parse(parameter);
        return {
          mapId: Number(parsed.mapId || 1),
          name: String(parsed.name || ''),
          x: Number(parsed.x || 0),
          y: Number(parsed.y || 0),
          switchId: Number(parsed.switchId || 0),
        };
      })(e || '{}');
    }),
    commonEvent: Number(pluginParameters.commonEvent || 0),
  };

  const PLUGIN_COMMAND_NAME = {
    RESERVE_TELEPORT: 'reserveTeleport',
    TELEPORT: 'teleport',
    REGISTER_POINT: 'registerPoint',
  };

  const FADE_TYPE = {
    DARK: 0,
  };

  class Scene_Teleport extends Scene_MenuBase {
    create() {
      super.create();
      this.createTeleportPointWindow();
    }

    createTeleportPointWindow() {
      this._teleportPointWindow = new Window_TeleportPoint(this.teleportPointWindowRect(), this.teleportPoints());
      this._teleportPointWindow.setHandler('ok', this.onTeleportPointOk.bind(this));
      this._teleportPointWindow.setHandler('cancel', this.popScene.bind(this));
      this.addWindow(this._teleportPointWindow);
    }

    teleportPointWindowRect() {
      const width = this.mainCommandWidth();
      const height = this.calcWindowHeight(this.teleportPoints().length + 1, true);
      return new Rectangle((Graphics.boxWidth - width) / 2, (Graphics.boxHeight - height) / 2, width, height);
    }

    onTeleportPointOk() {
      const point = this._teleportPointWindow.item();
      if (point) {
        $gameTemp.reserveTeleport(point);
        if (settings.commonEvent > 0) {
          $gameTemp.reserveCommonEvent(settings.commonEvent);
        }
      }
      SceneManager.goto(Scene_Map);
    }

    /**
     * @return {TeleportPoint[]}
     */
    teleportPoints() {
      return teleportPoints();
    }
  }

  class Scene_OverwriteTeleportPoint extends Scene_Teleport {
    onTeleportPointOk() {
      const index = this._teleportPointWindow.index();
      if (index < settings.maxRegisterCount || settings.maxRegisterCount === 0) {
        $gameSystem.registerTeleportPoint(TeleportPoint.fromPlayerPoint(), this._teleportPointWindow.index());
      }
      this.popScene();
    }

    teleportPoints() {
      return $gameSystem.registeredTeleportPoints();
    }
  }

  window.Scene_Teleport = Scene_Teleport;
  window.Scene_OverwriteTeleportPoint = Scene_OverwriteTeleportPoint;

  class Window_TeleportPoint extends Window_Command {
    /**
     * @param {Rectangle} rect 矩形
     * @param {TeleportPoint[]} data テレポ先一覧
     */
    initialize(rect, data) {
      this._data = data;
      super.initialize(rect);
    }

    makeCommandList() {
      this._data.forEach((point) => {
        this.addCommand(point.name, 'ok');
      });
      this.addCommand('캔슬', 'cancel');
    }

    maxItems() {
      return this._data.length + 1;
    }

    itemAt(index) {
      return this._data[index];
    }

    /**
     * @return {TeleportPoint}
     */
    item() {
      return this.itemAt(this.index());
    }
  }

  function teleportPoints() {
    return $gameSystem
      .registeredTeleportPoints()
      .concat(
        settings.fixedPoints.map(
          (fixedPoint) =>
            new TeleportPoint(fixedPoint.mapId, fixedPoint.name, fixedPoint.x, fixedPoint.y, fixedPoint.switchId)
        )
      )
      .filter((point) => point.isEnabled());
  }
  PluginManager.registerCommand(pluginName, PLUGIN_COMMAND_NAME.RESERVE_TELEPORT, function () {
    // テレポ先選択ウィンドウを開く
    if (!$gameParty.inBattle()) {
      SceneManager.push(Scene_Teleport);
    }
  });

  PluginManager.registerCommand(pluginName, PLUGIN_COMMAND_NAME.TELEPORT, function () {
    $gameTemp.teleport();
  });

  PluginManager.registerCommand(pluginName, PLUGIN_COMMAND_NAME.REGISTER_POINT, function (args) {
    const point = args.name ? TeleportPoint.fromPlayerPoint().withName(args.name) : TeleportPoint.fromPlayerPoint();
    if ($gameSystem.isRegistered(point)) {
      return;
    }
    if ($gameSystem.registeredTeleportPoints().length >= settings.maxRegisterCount) {
      SceneManager.push(Scene_OverwriteTeleportPoint);
    } else {
      // テレポ先登録
      $gameSystem.registerTeleportPoint(point);
    }
  });

  class TeleportPoint {
    /**
     * @param {number} mapId マップID
     * @param {string} name マップ名
     * @param {number} x X座標
     * @param {number} y Y座標
     * @param {number} switchId スイッチID
     */
    constructor(mapId, name, x, y, switchId) {
      this._mapId = mapId;
      this._name = name;
      this._x = x;
      this._y = y;
      this._switchId = switchId;
    }

    /**
     * プレイヤーのいる座標から生成
     * @return {TeleportPoint}
     */
    static fromPlayerPoint() {
      return new TeleportPoint($gameMap.mapId(), $gameMap.displayName(), $gamePlayer.x, $gamePlayer.y, 0);
    }

    get mapId() {
      return this._mapId;
    }

    get name() {
      return this._name;
    }

    get x() {
      return this._x;
    }

    get y() {
      return this._y;
    }

    /**
     * @param {string} name 名前
     * @return {TeleportPoint}
     */
    withName(name) {
      this._name = name;
      return this;
    }

    /**
     * @returns {boolean}
     */
    isEnabled() {
      return this._switchId === 0 || $gameSwitches.value(this._switchId);
    }

    transfer() {
      $gamePlayer.reserveTransfer(this._mapId, this._x, this._y, $gamePlayer.direction(), FADE_TYPE.DARK);
    }

    /**
     * @param {TeleportPoint} point テレポ先
     * @return {boolean}
     */
    equals(point) {
      return this._mapId === point.mapId && this._name === point.name;
    }
  }

  window.TeleportPoint = TeleportPoint;

  const _Game_Temp_initialize = Game_Temp.prototype.initialize;
  Game_Temp.prototype.initialize = function () {
    _Game_Temp_initialize.call(this);
    this._reservedTeleportPoint = null;
  };

  Game_Temp.prototype.reserveTeleport = function (teleportPoint) {
    this._reservedTeleportPoint = teleportPoint;
  };

  Game_Temp.prototype.teleport = function () {
    if (this.isTeleportReserved()) {
      this.reservedTeleportPoint().transfer();
      this._reservedTeleportPoint = null;
    }
  };

  Game_Temp.prototype.reservedTeleportPoint = function () {
    return this._reservedTeleportPoint;
  };

  Game_Temp.prototype.isTeleportReserved = function () {
    return !!this._reservedTeleportPoint;
  };

  const _Game_System_initialize = Game_System.prototype.initialize;
  Game_System.prototype.initialize = function () {
    _Game_System_initialize.call(this);
    this._teleportPoints = [];
  };

  Game_System.prototype.registeredTeleportPoints = function () {
    if (!this._teleportPoints) {
      this._teleportPoints = [];
    }
    return this._teleportPoints;
  };

  Game_System.prototype.registerTeleportPoint = function (teleportPoint, index) {
    if (Number.isFinite(index) && index < this._teleportPoints.length) {
      this._teleportPoints[index] = teleportPoint;
    } else {
      this._teleportPoints.push(teleportPoint);
    }
  };

  Game_System.prototype.isRegistered = function (teleport) {
    return this.registeredTeleportPoints().some((point) => point.equals(teleport));
  };
})();
