code:
//=============================================================================
// rmmz_managers.js v1.6.0
//=============================================================================

//-----------------------------------------------------------------------------
// DataManager
//
// The static class that manages the database and game objects.

function DataManager() {
  throw new Error("This is a static class");
}

$dataActors = null;
$dataClasses = null;
$dataSkills = null;
$dataItems = null;
$dataWeapons = null;
$dataArmors = null;
$dataEnemies = null;
$dataTroops = null;
$dataStates = null;
$dataAnimations = null;
$dataTilesets = null;
$dataCommonEvents = null;
$dataSystem = null;
$dataMapInfos = null;
$dataMap = null;
$gameTemp = null;
$gameSystem = null;
$gameScreen = null;
$gameTimer = null;
$gameMessage = null;
$gameSwitches = null;
$gameVariables = null;
$gameSelfSwitches = null;
$gameActors = null;
$gameParty = null;
$gameTroop = null;
$gameMap = null;
$gamePlayer = null;
$testEvent = null;

DataManager._globalInfo = null;
DataManager._errors = [];

DataManager._databaseFiles = [
  { name: "$dataActors", src: "Actors.json" },
  { name: "$dataClasses", src: "Classes.json" },
  { name: "$dataSkills", src: "Skills.json" },
  { name: "$dataItems", src: "Items.json" },
  { name: "$dataWeapons", src: "Weapons.json" },
  { name: "$dataArmors", src: "Armors.json" },
  { name: "$dataEnemies", src: "Enemies.json" },
  { name: "$dataTroops", src: "Troops.json" },
  { name: "$dataStates", src: "States.json" },
  { name: "$dataAnimations", src: "Animations.json" },
  { name: "$dataTilesets", src: "Tilesets.json" },
  { name: "$dataCommonEvents", src: "CommonEvents.json" },
  { name: "$dataSystem", src: "System.json" },
  { name: "$dataMapInfos", src: "MapInfos.json" },
];

DataManager.loadGlobalInfo = function () {
  StorageManager.loadObject("global")
    .then((globalInfo) => {
      this._globalInfo = globalInfo;
      this.removeInvalidGlobalInfo();
      return 0;
    })
    .catch(() => {
      this._globalInfo = [];
    });
};

DataManager.removeInvalidGlobalInfo = function () {
  const globalInfo = this._globalInfo;
  for (const info of globalInfo) {
    const savefileId = globalInfo.indexOf(info);
    if (!this.savefileExists(savefileId)) {
      delete globalInfo[savefileId];
    }
  }
};

DataManager.saveGlobalInfo = function () {
  StorageManager.saveObject("global", this._globalInfo);
};

DataManager.isGlobalInfoLoaded = function () {
  return !!this._globalInfo;
};

DataManager.loadDatabase = function () {
  const test = this.isBattleTest() || this.isEventTest();
  const prefix = test ? "Test_" : "";
  for (const databaseFile of this._databaseFiles) {
    this.loadDataFile(databaseFile.name, prefix + databaseFile.src);
  }
  if (this.isEventTest()) {
    this.loadDataFile("$testEvent", prefix + "Event.json");
  }
};

DataManager.loadDataFile = function (name, src) {
  const xhr = new XMLHttpRequest();
  const url = "data/" + src;
  window[name] = null;
  xhr.open("GET", url);
  xhr.overrideMimeType("application/json");
  xhr.onload = () => this.onXhrLoad(xhr, name, src, url);
  xhr.onerror = () => this.onXhrError(name, src, url);
  xhr.send();
};

DataManager.onXhrLoad = function (xhr, name, src, url) {
  if (xhr.status < 400) {
    window[name] = JSON.parse(xhr.responseText);
    this.onLoad(window[name]);
  } else {
    this.onXhrError(name, src, url);
  }
};

DataManager.onXhrError = function (name, src, url) {
  const error = { name: name, src: src, url: url };
  this._errors.push(error);
};

DataManager.isDatabaseLoaded = function () {
  this.checkError();
  for (const databaseFile of this._databaseFiles) {
    if (!window[databaseFile.name]) {
      return false;
    }
  }
  return true;
};

DataManager.loadMapData = function (mapId) {
  if (mapId > 0) {
    const filename = "Map%1.json".format(mapId.padZero(3));
    this.loadDataFile("$dataMap", filename);
  } else {
    this.makeEmptyMap();
  }
};

DataManager.makeEmptyMap = function () {
  $dataMap = {};
  $dataMap.data = [];
  $dataMap.events = [];
  $dataMap.width = 100;
  $dataMap.height = 100;
  $dataMap.scrollType = 3;
};

DataManager.isMapLoaded = function () {
  this.checkError();
  return !!$dataMap;
};

DataManager.onLoad = function (object) {
  if (this.isMapObject(object)) {
    this.extractMetadata(object);
    this.extractArrayMetadata(object.events);
  } else {
    this.extractArrayMetadata(object);
  }
};

DataManager.isMapObject = function (object) {
  return !!(object.data && object.events);
};

DataManager.extractArrayMetadata = function (array) {
  if (Array.isArray(array)) {
    for (const data of array) {
      if (data && "note" in data) {
        this.extractMetadata(data);
      }
    }
  }
};

DataManager.extractMetadata = function (data) {
  const regExp = /<([^<>:]+)(:?)([^>]*)>/g;
  data.meta = {};
  for (;;) {
    const match = regExp.exec(data.note);
    if (match) {
      if (match[2] === ":") {
        data.meta[match[1]] = match[3];
      } else {
        data.meta[match[1]] = true;
      }
    } else {
      break;
    }
  }
};

DataManager.checkError = function () {
  if (this._errors.length > 0) {
    const error = this._errors.shift();
    const retry = () => {
      this.loadDataFile(error.name, error.src);
    };
    throw ["LoadError", error.url, retry];
  }
};

DataManager.isBattleTest = function () {
  return Utils.isOptionValid("btest");
};

DataManager.isEventTest = function () {
  return Utils.isOptionValid("etest");
};

DataManager.isSkill = function (item) {
  return item && $dataSkills.includes(item);
};

DataManager.isItem = function (item) {
  return item && $dataItems.includes(item);
};

DataManager.isWeapon = function (item) {
  return item && $dataWeapons.includes(item);
};

DataManager.isArmor = function (item) {
  return item && $dataArmors.includes(item);
};

DataManager.createGameObjects = function () {
  $gameTemp = new Game_Temp();
  $gameSystem = new Game_System();
  $gameScreen = new Game_Screen();
  $gameTimer = new Game_Timer();
  $gameMessage = new Game_Message();
  $gameSwitches = new Game_Switches();
  $gameVariables = new Game_Variables();
  $gameSelfSwitches = new Game_SelfSwitches();
  $gameActors = new Game_Actors();
  $gameParty = new Game_Party();
  $gameTroop = new Game_Troop();
  $gameMap = new Game_Map();
  $gamePlayer = new Game_Player();
};

DataManager.setupNewGame = function () {
  this.createGameObjects();
  this.selectSavefileForNewGame();
  $gameParty.setupStartingMembers();
  $gamePlayer.setupForNewGame();
  Graphics.frameCount = 0;
};

DataManager.setupBattleTest = function () {
  this.createGameObjects();
  $gameParty.setupBattleTest();
  BattleManager.setup($dataSystem.testTroopId, true, false);
  BattleManager.setBattleTest(true);
  BattleManager.playBattleBgm();
};

DataManager.setupEventTest = function () {
  this.createGameObjects();
  this.selectSavefileForNewGame();
  $gameParty.setupStartingMembers();
  $gamePlayer.reserveTransfer(-1, 8, 6);
  $gamePlayer.setTransparent(false);
};

DataManager.isAnySavefileExists = function () {
  return this._globalInfo.some((x) => x);
};

DataManager.latestSavefileId = function () {
  const globalInfo = this._globalInfo;
  const validInfo = globalInfo.slice(1).filter((x) => x);
  const latest = Math.max(...validInfo.map((x) => x.timestamp));
  const index = globalInfo.findIndex((x) => x && x.timestamp === latest);
  return index > 0 ? index : 0;
};

DataManager.earliestSavefileId = function () {
  const globalInfo = this._globalInfo;
  const validInfo = globalInfo.slice(1).filter((x) => x);
  const earliest = Math.min(...validInfo.map((x) => x.timestamp));
  const index = globalInfo.findIndex((x) => x && x.timestamp === earliest);
  return index > 0 ? index : 0;
};

DataManager.emptySavefileId = function () {
  const globalInfo = this._globalInfo;
  const maxSavefiles = this.maxSavefiles();
  if (globalInfo.length < maxSavefiles) {
    return Math.max(1, globalInfo.length);
  } else {
    const index = globalInfo.slice(1).findIndex((x) => !x);
    return index >= 0 ? index + 1 : -1;
  }
};

DataManager.loadAllSavefileImages = function () {
  for (const info of this._globalInfo.filter((x) => x)) {
    this.loadSavefileImages(info);
  }
};

DataManager.loadSavefileImages = function (info) {
  if (info.characters && Symbol.iterator in info.characters) {
    for (const character of info.characters) {
      ImageManager.loadCharacter(character[0]);
    }
  }
  if (info.faces && Symbol.iterator in info.faces) {
    for (const face of info.faces) {
      ImageManager.loadFace(face[0]);
    }
  }
};

DataManager.maxSavefiles = function () {
  return 20;
};

DataManager.savefileInfo = function (savefileId) {
  const globalInfo = this._globalInfo;
  return globalInfo[savefileId] ? globalInfo[savefileId] : null;
};

DataManager.savefileExists = function (savefileId) {
  const saveName = this.makeSavename(savefileId);
  return StorageManager.exists(saveName);
};

DataManager.saveGame = function (savefileId) {
  const contents = this.makeSaveContents();
  const saveName = this.makeSavename(savefileId);
  return StorageManager.saveObject(saveName, contents).then(() => {
    this._globalInfo[savefileId] = this.makeSavefileInfo();
    this.saveGlobalInfo();
    return 0;
  });
};

DataManager.loadGame = function (savefileId) {
  const saveName = this.makeSavename(savefileId);
  return StorageManager.loadObject(saveName).then((contents) => {
    this.createGameObjects();
    this.extractSaveContents(contents);
    this.correctDataErrors();
    return 0;
  });
};

DataManager.makeSavename = function (savefileId) {
  return "file%1".format(savefileId);
};

DataManager.selectSavefileForNewGame = function () {
  const emptySavefileId = this.emptySavefileId();
  const earliestSavefileId = this.earliestSavefileId();
  if (emptySavefileId > 0) {
    $gameSystem.setSavefileId(emptySavefileId);
  } else {
    $gameSystem.setSavefileId(earliestSavefileId);
  }
};

DataManager.makeSavefileInfo = function () {
  const info = {};
  info.title = $dataSystem.gameTitle;
  info.characters = $gameParty.charactersForSavefile();
  info.faces = $gameParty.facesForSavefile();
  info.playtime = $gameSystem.playtimeText();
  info.timestamp = Date.now();
  return info;
};

DataManager.makeSaveContents = function () {
  // A save data does not contain $gameTemp, $gameMessage, and $gameTroop.
  const contents = {};
  contents.system = $gameSystem;
  contents.screen = $gameScreen;
  contents.timer = $gameTimer;
  contents.switches = $gameSwitches;
  contents.variables = $gameVariables;
  contents.selfSwitches = $gameSelfSwitches;
  contents.actors = $gameActors;
  contents.party = $gameParty;
  contents.map = $gameMap;
  contents.player = $gamePlayer;
  return contents;
};

DataManager.extractSaveContents = function (contents) {
  $gameSystem = contents.system;
  $gameScreen = contents.screen;
  $gameTimer = contents.timer;
  $gameSwitches = contents.switches;
  $gameVariables = contents.variables;
  $gameSelfSwitches = contents.selfSwitches;
  $gameActors = contents.actors;
  $gameParty = contents.party;
  $gameMap = contents.map;
  $gamePlayer = contents.player;
};

DataManager.correctDataErrors = function () {
  $gameParty.removeInvalidMembers();
};

//-----------------------------------------------------------------------------
// ConfigManager
//
// The static class that manages the configuration data.

function ConfigManager() {
  throw new Error("This is a static class");
}

ConfigManager.alwaysDash = false;
ConfigManager.commandRemember = false;
ConfigManager.touchUI = true;
ConfigManager._isLoaded = false;

Object.defineProperty(ConfigManager, "bgmVolume", {
  get: function () {
    return AudioManager._bgmVolume;
  },
  set: function (value) {
    AudioManager.bgmVolume = value;
  },
  configurable: true,
});

Object.defineProperty(ConfigManager, "bgsVolume", {
  get: function () {
    return AudioManager.bgsVolume;
  },
  set: function (value) {
    AudioManager.bgsVolume = value;
  },
  configurable: true,
});

Object.defineProperty(ConfigManager, "meVolume", {
  get: function () {
    return AudioManager.meVolume;
  },
  set: function (value) {
    AudioManager.meVolume = value;
  },
  configurable: true,
});

Object.defineProperty(ConfigManager, "seVolume", {
  get: function () {
    return AudioManager.seVolume;
  },
  set: function (value) {
    AudioManager.seVolume = value;
  },
  configurable: true,
});

ConfigManager.load = function () {
  StorageManager.loadObject("config")
    .then((config) => this.applyData(config || {}))
    .catch(() => 0)
    .then(() => {
      this._isLoaded = true;
      return 0;
    })
    .catch(() => 0);
};

ConfigManager.save = function () {
  StorageManager.saveObject("config", this.makeData());
};

ConfigManager.isLoaded = function () {
  return this._isLoaded;
};

ConfigManager.makeData = function () {
  const config = {};
  config.alwaysDash = this.alwaysDash;
  config.commandRemember = this.commandRemember;
  config.touchUI = this.touchUI;
  config.bgmVolume = this.bgmVolume;
  config.bgsVolume = this.bgsVolume;
  config.meVolume = this.meVolume;
  config.seVolume = this.seVolume;
  return config;
};

ConfigManager.applyData = function (config) {
  this.alwaysDash = this.readFlag(config, "alwaysDash", false);
  this.commandRemember = this.readFlag(config, "commandRemember", false);
  this.touchUI = this.readFlag(config, "touchUI", true);
  this.bgmVolume = this.readVolume(config, "bgmVolume");
  this.bgsVolume = this.readVolume(config, "bgsVolume");
  this.meVolume = this.readVolume(config, "meVolume");
  this.seVolume = this.readVolume(config, "seVolume");
};

ConfigManager.readFlag = function (config, name, defaultValue) {
  if (name in config) {
    return !!config[name];
  } else {
    return defaultValue;
  }
};

ConfigManager.readVolume = function (config, name) {
  if (name in config) {
    return Number(config[name]).clamp(0, 100);
  } else {
    return 100;
  }
};

//-----------------------------------------------------------------------------
// StorageManager
//
// The static class that manages storage for saving game data.

function StorageManager() {
  throw new Error("This is a static class");
}

StorageManager._forageKeys = [];
StorageManager._forageKeysUpdated = false;

StorageManager.isLocalMode = function () {
  return Utils.isNwjs();
};

StorageManager.saveObject = function (saveName, object) {
  return this.objectToJson(object)
    .then((json) => this.jsonToZip(json))
    .then((zip) => this.saveZip(saveName, zip));
};

StorageManager.loadObject = function (saveName) {
  return this.loadZip(saveName)
    .then((zip) => this.zipToJson(zip))
    .then((json) => this.jsonToObject(json));
};

StorageManager.objectToJson = function (object) {
  return new Promise((resolve, reject) => {
    try {
      const json = JsonEx.stringify(object);
      resolve(json);
    } catch (e) {
      reject(e);
    }
  });
};

StorageManager.jsonToObject = function (json) {
  return new Promise((resolve, reject) => {
    try {
      const object = JsonEx.parse(json);
      resolve(object);
    } catch (e) {
      reject(e);
    }
  });
};

StorageManager.jsonToZip = function (json) {
  return new Promise((resolve, reject) => {
    try {
      const zip = pako.deflate(json, { to: "string", level: 1 });
      if (zip.length >= 50000) {
        console.warn("Save data is too big.");
      }
      resolve(zip);
    } catch (e) {
      reject(e);
    }
  });
};

StorageManager.zipToJson = function (zip) {
  return new Promise((resolve, reject) => {
    try {
      if (zip) {
        const json = pako.inflate(zip, { to: "string" });
        resolve(json);
      } else {
        resolve("null");
      }
    } catch (e) {
      reject(e);
    }
  });
};

StorageManager.saveZip = function (saveName, zip) {
  if (this.isLocalMode()) {
    return this.saveToLocalFile(saveName, zip);
  } else {
    return this.saveToForage(saveName, zip);
  }
};

StorageManager.loadZip = function (saveName) {
  if (this.isLocalMode()) {
    return this.loadFromLocalFile(saveName);
  } else {
    return this.loadFromForage(saveName);
  }
};

StorageManager.exists = function (saveName) {
  if (this.isLocalMode()) {
    return this.localFileExists(saveName);
  } else {
    return this.forageExists(saveName);
  }
};

StorageManager.remove = function (saveName) {
  if (this.isLocalMode()) {
    return this.removeLocalFile(saveName);
  } else {
    return this.removeForage(saveName);
  }
};

StorageManager.saveToLocalFile = function (saveName, zip) {
  const dirPath = this.fileDirectoryPath();
  const filePath = this.filePath(saveName);
  const backupFilePath = filePath + "_";
  return new Promise((resolve, reject) => {
    this.fsMkdir(dirPath);
    this.fsUnlink(backupFilePath);
    this.fsRename(filePath, backupFilePath);
    try {
      this.fsWriteFile(filePath, zip);
      this.fsUnlink(backupFilePath);
      resolve();
    } catch (e) {
      try {
        this.fsUnlink(filePath);
        this.fsRename(backupFilePath, filePath);
      } catch (e2) {
        //
      }
      reject(e);
    }
  });
};

StorageManager.loadFromLocalFile = function (saveName) {
  const filePath = this.filePath(saveName);
  return new Promise((resolve, reject) => {
    const data = this.fsReadFile(filePath);
    if (data) {
      resolve(data);
    } else {
      reject(new Error("Savefile not found"));
    }
  });
};

StorageManager.localFileExists = function (saveName) {
  const fs = require("fs");
  return fs.existsSync(this.filePath(saveName));
};

StorageManager.removeLocalFile = function (saveName) {
  this.fsUnlink(this.filePath(saveName));
};

StorageManager.saveToForage = function (saveName, zip) {
  const key = this.forageKey(saveName);
  const testKey = this.forageTestKey();
  setTimeout(() => localforage.removeItem(testKey));
  return localforage
    .setItem(testKey, zip)
    .then(() => localforage.setItem(key, zip))
    .then(() => this.updateForageKeys());
};

StorageManager.loadFromForage = function (saveName) {
  const key = this.forageKey(saveName);
  return localforage.getItem(key);
};

StorageManager.forageExists = function (saveName) {
  const key = this.forageKey(saveName);
  return this._forageKeys.includes(key);
};

StorageManager.removeForage = function (saveName) {
  const key = this.forageKey(saveName);
  return localforage.removeItem(key).then(() => this.updateForageKeys());
};

StorageManager.updateForageKeys = function () {
  this._forageKeysUpdated = false;
  return localforage.keys().then((keys) => {
    this._forageKeys = keys;
    this._forageKeysUpdated = true;
    return 0;
  });
};

StorageManager.forageKeysUpdated = function () {
  return this._forageKeysUpdated;
};

StorageManager.fsMkdir = function (path) {
  const fs = require("fs");
  if (!fs.existsSync(path)) {
    fs.mkdirSync(path);
  }
};

StorageManager.fsRename = function (oldPath, newPath) {
  const fs = require("fs");
  if (fs.existsSync(oldPath)) {
    fs.renameSync(oldPath, newPath);
  }
};

StorageManager.fsUnlink = function (path) {
  const fs = require("fs");
  if (fs.existsSync(path)) {
    fs.unlinkSync(path);
  }
};

StorageManager.fsReadFile = function (path) {
  const fs = require("fs");
  if (fs.existsSync(path)) {
    return fs.readFileSync(path, { encoding: "utf8" });
  } else {
    return null;
  }
};

StorageManager.fsWriteFile = function (path, data) {
  const fs = require("fs");
  fs.writeFileSync(path, data);
};

StorageManager.fileDirectoryPath = function () {
  const path = require("path");
  const base = path.dirname(process.mainModule.filename);
  return path.join(base, "save/");
};

StorageManager.filePath = function (saveName) {
  const dir = this.fileDirectoryPath();
  return dir + saveName + ".rmmzsave";
};

StorageManager.forageKey = function (saveName) {
  const gameId = $dataSystem.advanced.gameId;
  return "rmmzsave." + gameId + "." + saveName;
};

StorageManager.forageTestKey = function () {
  return "rmmzsave.test";
};

//-----------------------------------------------------------------------------
// FontManager
//
// The static class that loads font files.

function FontManager() {
  throw new Error("This is a static class");
}

FontManager._urls = {};
FontManager._states = {};

FontManager.load = function (family, filename) {
  if (this._states[family] !== "loaded") {
    if (filename) {
      const url = this.makeUrl(filename);
      this.startLoading(family, url);
    } else {
      this._urls[family] = "";
      this._states[family] = "loaded";
    }
  }
};

FontManager.isReady = function () {
  for (const family in this._states) {
    const state = this._states[family];
    if (state === "loading") {
      return false;
    }
    if (state === "error") {
      this.throwLoadError(family);
    }
  }
  return true;
};

FontManager.startLoading = function (family, url) {
  const source = "url(" + url + ")";
  const font = new FontFace(family, source);
  this._urls[family] = url;
  this._states[family] = "loading";
  font
    .load()
    .then(() => {
      document.fonts.add(font);
      this._states[family] = "loaded";
      return 0;
    })
    .catch(() => {
      this._states[family] = "error";
    });
};

FontManager.throwLoadError = function (family) {
  const url = this._urls[family];
  const retry = () => this.startLoading(family, url);
  throw ["LoadError", url, retry];
};

FontManager.makeUrl = function (filename) {
  return "fonts/" + Utils.encodeURI(filename);
};

//-----------------------------------------------------------------------------
// ImageManager
//
// The static class that loads images, creates bitmap objects and retains them.

function ImageManager() {
  throw new Error("This is a static class");
}

ImageManager.iconWidth = 32;
ImageManager.iconHeight = 32;
ImageManager.faceWidth = 144;
ImageManager.faceHeight = 144;

ImageManager._cache = {};
ImageManager._system = {};
ImageManager._emptyBitmap = new Bitmap(1, 1);

ImageManager.loadAnimation = function (filename) {
  return this.loadBitmap("img/animations/", filename);
};

ImageManager.loadBattleback1 = function (filename) {
  return this.loadBitmap("img/battlebacks1/", filename);
};

ImageManager.loadBattleback2 = function (filename) {
  return this.loadBitmap("img/battlebacks2/", filename);
};

ImageManager.loadEnemy = function (filename) {
  return this.loadBitmap("img/enemies/", filename);
};

ImageManager.loadCharacter = function (filename) {
  return this.loadBitmap("img/characters/", filename);
};

ImageManager.loadFace = function (filename) {
  return this.loadBitmap("img/faces/", filename);
};

ImageManager.loadParallax = function (filename) {
  return this.loadBitmap("img/parallaxes/", filename);
};

ImageManager.loadPicture = function (filename) {
  return this.loadBitmap("img/pictures/", filename);
};

ImageManager.loadSvActor = function (filename) {
  return this.loadBitmap("img/sv_actors/", filename);
};

ImageManager.loadSvEnemy = function (filename) {
  return this.loadBitmap("img/sv_enemies/", filename);
};

ImageManager.loadSystem = function (filename) {
  return this.loadBitmap("img/system/", filename);
};

ImageManager.loadTileset = function (filename) {
  return this.loadBitmap("img/tilesets/", filename);
};

ImageManager.loadTitle1 = function (filename) {
  return this.loadBitmap("img/titles1/", filename);
};

ImageManager.loadTitle2 = function (filename) {
  return this.loadBitmap("img/titles2/", filename);
};

ImageManager.loadBitmap = function (folder, filename) {
  if (filename) {
    const url = folder + Utils.encodeURI(filename) + ".png";
    return this.loadBitmapFromUrl(url);
  } else {
    return this._emptyBitmap;
  }
};

ImageManager.loadBitmapFromUrl = function (url) {
  const cache = url.includes("/system/") ? this._system : this._cache;
  if (!cache[url]) {
    cache[url] = Bitmap.load(url);
  }
  return cache[url];
};

ImageManager.clear = function () {
  const cache = this._cache;
  for (const url in cache) {
    cache[url].destroy();
  }
  this._cache = {};
};

ImageManager.isReady = function () {
  for (const cache of [this._cache, this._system]) {
    for (const url in cache) {
      const bitmap = cache[url];
      if (bitmap.isError()) {
        this.throwLoadError(bitmap);
      }
      if (!bitmap.isReady()) {
        return false;
      }
    }
  }
  return true;
};

ImageManager.throwLoadError = function (bitmap) {
  const retry = bitmap.retry.bind(bitmap);
  throw ["LoadError", bitmap.url, retry];
};

ImageManager.isObjectCharacter = function (filename) {
  const sign = Utils.extractFileName(filename).match(/^[!$]+/);
  return sign && sign[0].includes("!");
};

ImageManager.isBigCharacter = function (filename) {
  const sign = Utils.extractFileName(filename).match(/^[!$]+/);
  return sign && sign[0].includes("$");
};

ImageManager.isZeroParallax = function (filename) {
  return Utils.extractFileName(filename).charAt(0) === "!";
};

//-----------------------------------------------------------------------------
// EffectManager
//
// The static class that loads Effekseer effects.

function EffectManager() {
  throw new Error("This is a static class");
}

EffectManager._cache = {};
EffectManager._errorUrls = [];

EffectManager.load = function (filename) {
  if (filename) {
    const url = this.makeUrl(filename);
    const cache = this._cache;
    if (!cache[url] && Graphics.effekseer) {
      this.startLoading(url);
    }
    return cache[url];
  } else {
    return null;
  }
};

EffectManager.startLoading = function (url) {
  const onLoad = () => this.onLoad(url);
  const onError = (message, url) => this.onError(url);
  const effect = Graphics.effekseer.loadEffect(url, 1, onLoad, onError);
  this._cache[url] = effect;
  return effect;
};

EffectManager.clear = function () {
  for (const url in this._cache) {
    const effect = this._cache[url];
    Graphics.effekseer.releaseEffect(effect);
  }
  this._cache = {};
};

EffectManager.onLoad = function (/*url*/) {
  //
};

EffectManager.onError = function (url) {
  this._errorUrls.push(url);
};

EffectManager.makeUrl = function (filename) {
  return "effects/" + Utils.encodeURI(filename) + ".efkefc";
};

EffectManager.checkErrors = function () {
  const url = this._errorUrls.shift();
  if (url) {
    this.throwLoadError(url);
  }
};

EffectManager.throwLoadError = function (url) {
  const retry = () => this.startLoading(url);
  throw ["LoadError", url, retry];
};

EffectManager.isReady = function () {
  this.checkErrors();
  for (const url in this._cache) {
    const effect = this._cache[url];
    if (!effect.isLoaded) {
      return false;
    }
  }
  return true;
};

//-----------------------------------------------------------------------------
// AudioManager
//
// The static class that handles BGM, BGS, ME and SE.

function AudioManager() {
  throw new Error("This is a static class");
}

AudioManager._bgmVolume = 100;
AudioManager._bgsVolume = 100;
AudioManager._meVolume = 100;
AudioManager._seVolume = 100;
AudioManager._currentBgm = null;
AudioManager._currentBgs = null;
AudioManager._bgmBuffer = null;
AudioManager._bgsBuffer = null;
AudioManager._meBuffer = null;
AudioManager._seBuffers = [];
AudioManager._staticBuffers = [];
AudioManager._replayFadeTime = 0.5;
AudioManager._path = "audio/";

Object.defineProperty(AudioManager, "bgmVolume", {
  get: function () {
    return this._bgmVolume;
  },
  set: function (value) {
    this._bgmVolume = value;
    this.updateBgmParameters(this._currentBgm);
  },
  configurable: true,
});

Object.defineProperty(AudioManager, "bgsVolume", {
  get: function () {
    return this._bgsVolume;
  },
  set: function (value) {
    this._bgsVolume = value;
    this.updateBgsParameters(this._currentBgs);
  },
  configurable: true,
});

Object.defineProperty(AudioManager, "meVolume", {
  get: function () {
    return this._meVolume;
  },
  set: function (value) {
    this._meVolume = value;
    this.updateMeParameters(this._currentMe);
  },
  configurable: true,
});

Object.defineProperty(AudioManager, "seVolume", {
  get: function () {
    return this._seVolume;
  },
  set: function (value) {
    this._seVolume = value;
  },
  configurable: true,
});

AudioManager.playBgm = function (bgm, pos) {
  if (this.isCurrentBgm(bgm)) {
    this.updateBgmParameters(bgm);
  } else {
    this.stopBgm();
    if (bgm.name) {
      this._bgmBuffer = this.createBuffer("bgm/", bgm.name);
      this.updateBgmParameters(bgm);
      if (!this._meBuffer) {
        this._bgmBuffer.play(true, pos || 0);
      }
    }
  }
  this.updateCurrentBgm(bgm, pos);
};

AudioManager.replayBgm = function (bgm) {
  if (this.isCurrentBgm(bgm)) {
    this.updateBgmParameters(bgm);
  } else {
    this.playBgm(bgm, bgm.pos);
    if (this._bgmBuffer) {
      this._bgmBuffer.fadeIn(this._replayFadeTime);
    }
  }
};

AudioManager.isCurrentBgm = function (bgm) {
  return (
    this._currentBgm && this._bgmBuffer && this._currentBgm.name === bgm.name
  );
};

AudioManager.updateBgmParameters = function (bgm) {
  this.updateBufferParameters(this._bgmBuffer, this._bgmVolume, bgm);
};

AudioManager.updateCurrentBgm = function (bgm, pos) {
  this._currentBgm = {
    name: bgm.name,
    volume: bgm.volume,
    pitch: bgm.pitch,
    pan: bgm.pan,
    pos: pos,
  };
};

AudioManager.stopBgm = function () {
  if (this._bgmBuffer) {
    this._bgmBuffer.destroy();
    this._bgmBuffer = null;
    this._currentBgm = null;
  }
};

AudioManager.fadeOutBgm = function (duration) {
  if (this._bgmBuffer && this._currentBgm) {
    this._bgmBuffer.fadeOut(duration);
    this._currentBgm = null;
  }
};

AudioManager.fadeInBgm = function (duration) {
  if (this._bgmBuffer && this._currentBgm) {
    this._bgmBuffer.fadeIn(duration);
  }
};

AudioManager.playBgs = function (bgs, pos) {
  if (this.isCurrentBgs(bgs)) {
    this.updateBgsParameters(bgs);
  } else {
    this.stopBgs();
    if (bgs.name) {
      this._bgsBuffer = this.createBuffer("bgs/", bgs.name);
      this.updateBgsParameters(bgs);
      this._bgsBuffer.play(true, pos || 0);
    }
  }
  this.updateCurrentBgs(bgs, pos);
};

AudioManager.replayBgs = function (bgs) {
  if (this.isCurrentBgs(bgs)) {
    this.updateBgsParameters(bgs);
  } else {
    this.playBgs(bgs, bgs.pos);
    if (this._bgsBuffer) {
      this._bgsBuffer.fadeIn(this._replayFadeTime);
    }
  }
};

AudioManager.isCurrentBgs = function (bgs) {
  return (
    this._currentBgs && this._bgsBuffer && this._currentBgs.name === bgs.name
  );
};

AudioManager.updateBgsParameters = function (bgs) {
  this.updateBufferParameters(this._bgsBuffer, this._bgsVolume, bgs);
};

AudioManager.updateCurrentBgs = function (bgs, pos) {
  this._currentBgs = {
    name: bgs.name,
    volume: bgs.volume,
    pitch: bgs.pitch,
    pan: bgs.pan,
    pos: pos,
  };
};

AudioManager.stopBgs = function () {
  if (this._bgsBuffer) {
    this._bgsBuffer.destroy();
    this._bgsBuffer = null;
    this._currentBgs = null;
  }
};

AudioManager.fadeOutBgs = function (duration) {
  if (this._bgsBuffer && this._currentBgs) {
    this._bgsBuffer.fadeOut(duration);
    this._currentBgs = null;
  }
};

AudioManager.fadeInBgs = function (duration) {
  if (this._bgsBuffer && this._currentBgs) {
    this._bgsBuffer.fadeIn(duration);
  }
};

AudioManager.playMe = function (me) {
  this.stopMe();
  if (me.name) {
    if (this._bgmBuffer && this._currentBgm) {
      this._currentBgm.pos = this._bgmBuffer.seek();
      this._bgmBuffer.stop();
    }
    this._meBuffer = this.createBuffer("me/", me.name);
    this.updateMeParameters(me);
    this._meBuffer.play(false);
    this._meBuffer.addStopListener(this.stopMe.bind(this));
  }
};

AudioManager.updateMeParameters = function (me) {
  this.updateBufferParameters(this._meBuffer, this._meVolume, me);
};

AudioManager.fadeOutMe = function (duration) {
  if (this._meBuffer) {
    this._meBuffer.fadeOut(duration);
  }
};

AudioManager.stopMe = function () {
  if (this._meBuffer) {
    this._meBuffer.destroy();
    this._meBuffer = null;
    if (this._bgmBuffer && this._currentBgm && !this._bgmBuffer.isPlaying()) {
      this._bgmBuffer.play(true, this._currentBgm.pos);
      this._bgmBuffer.fadeIn(this._replayFadeTime);
    }
  }
};

AudioManager.playSe = function (se) {
  if (se.name) {
    // [Note] Do not play the same sound in the same frame.
    const latestBuffers = this._seBuffers.filter(
      (buffer) => buffer.frameCount === Graphics.frameCount
    );
    if (latestBuffers.find((buffer) => buffer.name === se.name)) {
      return;
    }
    const buffer = this.createBuffer("se/", se.name);
    this.updateSeParameters(buffer, se);
    buffer.play(false);
    this._seBuffers.push(buffer);
    this.cleanupSe();
  }
};

AudioManager.updateSeParameters = function (buffer, se) {
  this.updateBufferParameters(buffer, this._seVolume, se);
};

AudioManager.cleanupSe = function () {
  for (const buffer of this._seBuffers) {
    if (!buffer.isPlaying()) {
      buffer.destroy();
    }
  }
  this._seBuffers = this._seBuffers.filter((buffer) => buffer.isPlaying());
};

AudioManager.stopSe = function () {
  for (const buffer of this._seBuffers) {
    buffer.destroy();
  }
  this._seBuffers = [];
};

AudioManager.playStaticSe = function (se) {
  if (se.name) {
    this.loadStaticSe(se);
    for (const buffer of this._staticBuffers) {
      if (buffer.name === se.name) {
        buffer.stop();
        this.updateSeParameters(buffer, se);
        buffer.play(false);
        break;
      }
    }
  }
};

AudioManager.loadStaticSe = function (se) {
  if (se.name && !this.isStaticSe(se)) {
    const buffer = this.createBuffer("se/", se.name);
    this._staticBuffers.push(buffer);
  }
};

AudioManager.isStaticSe = function (se) {
  for (const buffer of this._staticBuffers) {
    if (buffer.name === se.name) {
      return true;
    }
  }
  return false;
};

AudioManager.stopAll = function () {
  this.stopMe();
  this.stopBgm();
  this.stopBgs();
  this.stopSe();
};

AudioManager.saveBgm = function () {
  if (this._currentBgm) {
    const bgm = this._currentBgm;
    return {
      name: bgm.name,
      volume: bgm.volume,
      pitch: bgm.pitch,
      pan: bgm.pan,
      pos: this._bgmBuffer ? this._bgmBuffer.seek() : 0,
    };
  } else {
    return this.makeEmptyAudioObject();
  }
};

AudioManager.saveBgs = function () {
  if (this._currentBgs) {
    const bgs = this._currentBgs;
    return {
      name: bgs.name,
      volume: bgs.volume,
      pitch: bgs.pitch,
      pan: bgs.pan,
      pos: this._bgsBuffer ? this._bgsBuffer.seek() : 0,
    };
  } else {
    return this.makeEmptyAudioObject();
  }
};

AudioManager.makeEmptyAudioObject = function () {
  return { name: "", volume: 0, pitch: 0 };
};

AudioManager.createBuffer = function (folder, name) {
  const ext = this.audioFileExt();
  const url = this._path + folder + Utils.encodeURI(name) + ext;
  const buffer = new WebAudio(url);
  buffer.name = name;
  buffer.frameCount = Graphics.frameCount;
  return buffer;
};

AudioManager.updateBufferParameters = function (buffer, configVolume, audio) {
  if (buffer && audio) {
    buffer.volume = (configVolume * (audio.volume || 0)) / 10000;
    buffer.pitch = (audio.pitch || 0) / 100;
    buffer.pan = (audio.pan || 0) / 100;
  }
};

AudioManager.audioFileExt = function () {
  return ".ogg";
};

AudioManager.checkErrors = function () {
  const buffers = [this._bgmBuffer, this._bgsBuffer, this._meBuffer];
  buffers.push(...this._seBuffers);
  buffers.push(...this._staticBuffers);
  for (const buffer of buffers) {
    if (buffer && buffer.isError()) {
      this.throwLoadError(buffer);
    }
  }
};

AudioManager.throwLoadError = function (webAudio) {
  const retry = webAudio.retry.bind(webAudio);
  throw ["LoadError", webAudio.url, retry];
};

//-----------------------------------------------------------------------------
// SoundManager
//
// The static class that plays sound effects defined in the database.

function SoundManager() {
  throw new Error("This is a static class");
}

SoundManager.preloadImportantSounds = function () {
  this.loadSystemSound(0);
  this.loadSystemSound(1);
  this.loadSystemSound(2);
  this.loadSystemSound(3);
};

SoundManager.loadSystemSound = function (n) {
  if ($dataSystem) {
    AudioManager.loadStaticSe($dataSystem.sounds[n]);
  }
};

SoundManager.playSystemSound = function (n) {
  if ($dataSystem) {
    AudioManager.playStaticSe($dataSystem.sounds[n]);
  }
};

SoundManager.playCursor = function () {
  this.playSystemSound(0);
};

SoundManager.playOk = function () {
  this.playSystemSound(1);
};

SoundManager.playCancel = function () {
  this.playSystemSound(2);
};

SoundManager.playBuzzer = function () {
  this.playSystemSound(3);
};

SoundManager.playEquip = function () {
  this.playSystemSound(4);
};

SoundManager.playSave = function () {
  this.playSystemSound(5);
};

SoundManager.playLoad = function () {
  this.playSystemSound(6);
};

SoundManager.playBattleStart = function () {
  this.playSystemSound(7);
};

SoundManager.playEscape = function () {
  this.playSystemSound(8);
};

SoundManager.playEnemyAttack = function () {
  this.playSystemSound(9);
};

SoundManager.playEnemyDamage = function () {
  this.playSystemSound(10);
};

SoundManager.playEnemyCollapse = function () {
  this.playSystemSound(11);
};

SoundManager.playBossCollapse1 = function () {
  this.playSystemSound(12);
};

SoundManager.playBossCollapse2 = function () {
  this.playSystemSound(13);
};

SoundManager.playActorDamage = function () {
  this.playSystemSound(14);
};

SoundManager.playActorCollapse = function () {
  this.playSystemSound(15);
};

SoundManager.playRecovery = function () {
  this.playSystemSound(16);
};

SoundManager.playMiss = function () {
  this.playSystemSound(17);
};

SoundManager.playEvasion = function () {
  this.playSystemSound(18);
};

SoundManager.playMagicEvasion = function () {
  this.playSystemSound(19);
};

SoundManager.playReflection = function () {
  this.playSystemSound(20);
};

SoundManager.playShop = function () {
  this.playSystemSound(21);
};

SoundManager.playUseItem = function () {
  this.playSystemSound(22);
};

SoundManager.playUseSkill = function () {
  this.playSystemSound(23);
};

//-----------------------------------------------------------------------------
// TextManager
//
// The static class that handles terms and messages.

function TextManager() {
  throw new Error("This is a static class");
}

TextManager.basic = function (basicId) {
  return $dataSystem.terms.basic[basicId] || "";
};

TextManager.param = function (paramId) {
  return $dataSystem.terms.params[paramId] || "";
};

TextManager.command = function (commandId) {
  return $dataSystem.terms.commands[commandId] || "";
};

TextManager.message = function (messageId) {
  return $dataSystem.terms.messages[messageId] || "";
};

TextManager.getter = function (method, param) {
  return {
    get: function () {
      return this[method](param);
    },
    configurable: true,
  };
};

Object.defineProperty(TextManager, "currencyUnit", {
  get: function () {
    return $dataSystem.currencyUnit;
  },
  configurable: true,
});

Object.defineProperties(TextManager, {
  level: TextManager.getter("basic", 0),
  levelA: TextManager.getter("basic", 1),
  hp: TextManager.getter("basic", 2),
  hpA: TextManager.getter("basic", 3),
  mp: TextManager.getter("basic", 4),
  mpA: TextManager.getter("basic", 5),
  tp: TextManager.getter("basic", 6),
  tpA: TextManager.getter("basic", 7),
  exp: TextManager.getter("basic", 8),
  expA: TextManager.getter("basic", 9),
  fight: TextManager.getter("command", 0),
  escape: TextManager.getter("command", 1),
  attack: TextManager.getter("command", 2),
  guard: TextManager.getter("command", 3),
  item: TextManager.getter("command", 4),
  skill: TextManager.getter("command", 5),
  equip: TextManager.getter("command", 6),
  status: TextManager.getter("command", 7),
  formation: TextManager.getter("command", 8),
  save: TextManager.getter("command", 9),
  gameEnd: TextManager.getter("command", 10),
  options: TextManager.getter("command", 11),
  weapon: TextManager.getter("command", 12),
  armor: TextManager.getter("command", 13),
  keyItem: TextManager.getter("command", 14),
  equip2: TextManager.getter("command", 15),
  optimize: TextManager.getter("command", 16),
  clear: TextManager.getter("command", 17),
  newGame: TextManager.getter("command", 18),
  continue_: TextManager.getter("command", 19),
  toTitle: TextManager.getter("command", 21),
  cancel: TextManager.getter("command", 22),
  buy: TextManager.getter("command", 24),
  sell: TextManager.getter("command", 25),
  alwaysDash: TextManager.getter("message", "alwaysDash"),
  commandRemember: TextManager.getter("message", "commandRemember"),
  touchUI: TextManager.getter("message", "touchUI"),
  bgmVolume: TextManager.getter("message", "bgmVolume"),
  bgsVolume: TextManager.getter("message", "bgsVolume"),
  meVolume: TextManager.getter("message", "meVolume"),
  seVolume: TextManager.getter("message", "seVolume"),
  possession: TextManager.getter("message", "possession"),
  expTotal: TextManager.getter("message", "expTotal"),
  expNext: TextManager.getter("message", "expNext"),
  saveMessage: TextManager.getter("message", "saveMessage"),
  loadMessage: TextManager.getter("message", "loadMessage"),
  file: TextManager.getter("message", "file"),
  autosave: TextManager.getter("message", "autosave"),
  partyName: TextManager.getter("message", "partyName"),
  emerge: TextManager.getter("message", "emerge"),
  preemptive: TextManager.getter("message", "preemptive"),
  surprise: TextManager.getter("message", "surprise"),
  escapeStart: TextManager.getter("message", "escapeStart"),
  escapeFailure: TextManager.getter("message", "escapeFailure"),
  victory: TextManager.getter("message", "victory"),
  defeat: TextManager.getter("message", "defeat"),
  obtainExp: TextManager.getter("message", "obtainExp"),
  obtainGold: TextManager.getter("message", "obtainGold"),
  obtainItem: TextManager.getter("message", "obtainItem"),
  levelUp: TextManager.getter("message", "levelUp"),
  obtainSkill: TextManager.getter("message", "obtainSkill"),
  useItem: TextManager.getter("message", "useItem"),
  criticalToEnemy: TextManager.getter("message", "criticalToEnemy"),
  criticalToActor: TextManager.getter("message", "criticalToActor"),
  actorDamage: TextManager.getter("message", "actorDamage"),
  actorRecovery: TextManager.getter("message", "actorRecovery"),
  actorGain: TextManager.getter("message", "actorGain"),
  actorLoss: TextManager.getter("message", "actorLoss"),
  actorDrain: TextManager.getter("message", "actorDrain"),
  actorNoDamage: TextManager.getter("message", "actorNoDamage"),
  actorNoHit: TextManager.getter("message", "actorNoHit"),
  enemyDamage: TextManager.getter("message", "enemyDamage"),
  enemyRecovery: TextManager.getter("message", "enemyRecovery"),
  enemyGain: TextManager.getter("message", "enemyGain"),
  enemyLoss: TextManager.getter("message", "enemyLoss"),
  enemyDrain: TextManager.getter("message", "enemyDrain"),
  enemyNoDamage: TextManager.getter("message", "enemyNoDamage"),
  enemyNoHit: TextManager.getter("message", "enemyNoHit"),
  evasion: TextManager.getter("message", "evasion"),
  magicEvasion: TextManager.getter("message", "magicEvasion"),
  magicReflection: TextManager.getter("message", "magicReflection"),
  counterAttack: TextManager.getter("message", "counterAttack"),
  substitute: TextManager.getter("message", "substitute"),
  buffAdd: TextManager.getter("message", "buffAdd"),
  debuffAdd: TextManager.getter("message", "debuffAdd"),
  buffRemove: TextManager.getter("message", "buffRemove"),
  actionFailure: TextManager.getter("message", "actionFailure"),
});

//-----------------------------------------------------------------------------
// ColorManager
//
// The static class that handles the window colors.

function ColorManager() {
  throw new Error("This is a static class");
}

ColorManager.loadWindowskin = function () {
  this._windowskin = ImageManager.loadSystem("Window");
};

ColorManager.textColor = function (n) {
  const px = 96 + (n % 8) * 12 + 6;
  const py = 144 + Math.floor(n / 8) * 12 + 6;
  return this._windowskin.getPixel(px, py);
};

ColorManager.normalColor = function () {
  return this.textColor(0);
};

ColorManager.systemColor = function () {
  return this.textColor(16);
};

ColorManager.crisisColor = function () {
  return this.textColor(17);
};

ColorManager.deathColor = function () {
  return this.textColor(18);
};

ColorManager.gaugeBackColor = function () {
  return this.textColor(19);
};

ColorManager.hpGaugeColor1 = function () {
  return this.textColor(20);
};

ColorManager.hpGaugeColor2 = function () {
  return this.textColor(21);
};

ColorManager.mpGaugeColor1 = function () {
  return this.textColor(22);
};

ColorManager.mpGaugeColor2 = function () {
  return this.textColor(23);
};

ColorManager.mpCostColor = function () {
  return this.textColor(23);
};

ColorManager.powerUpColor = function () {
  return this.textColor(24);
};

ColorManager.powerDownColor = function () {
  return this.textColor(25);
};

ColorManager.ctGaugeColor1 = function () {
  return this.textColor(26);
};

ColorManager.ctGaugeColor2 = function () {
  return this.textColor(27);
};

ColorManager.tpGaugeColor1 = function () {
  return this.textColor(28);
};

ColorManager.tpGaugeColor2 = function () {
  return this.textColor(29);
};

ColorManager.tpCostColor = function () {
  return this.textColor(29);
};

ColorManager.pendingColor = function () {
  return this._windowskin.getPixel(120, 120);
};

ColorManager.hpColor = function (actor) {
  if (!actor) {
    return this.normalColor();
  } else if (actor.isDead()) {
    return this.deathColor();
  } else if (actor.isDying()) {
    return this.crisisColor();
  } else {
    return this.normalColor();
  }
};

ColorManager.mpColor = function (/*actor*/) {
  return this.normalColor();
};

ColorManager.tpColor = function (/*actor*/) {
  return this.normalColor();
};

ColorManager.paramchangeTextColor = function (change) {
  if (change > 0) {
    return this.powerUpColor();
  } else if (change < 0) {
    return this.powerDownColor();
  } else {
    return this.normalColor();
  }
};

ColorManager.damageColor = function (colorType) {
  switch (colorType) {
    case 0: // HP damage
      return "#ffffff";
    case 1: // HP recover
      return "#b9ffb5";
    case 2: // MP damage
      return "#ffff90";
    case 3: // MP recover
      return "#80b0ff";
    default:
      return "#808080";
  }
};

ColorManager.outlineColor = function () {
  return "rgba(0, 0, 0, 0.6)";
};

ColorManager.dimColor1 = function () {
  return "rgba(0, 0, 0, 0.6)";
};

ColorManager.dimColor2 = function () {
  return "rgba(0, 0, 0, 0)";
};

ColorManager.itemBackColor1 = function () {
  return "rgba(32, 32, 32, 0.5)";
};

ColorManager.itemBackColor2 = function () {
  return "rgba(0, 0, 0, 0.5)";
};

//-----------------------------------------------------------------------------
// SceneManager
//
// The static class that manages scene transitions.

function SceneManager() {
  throw new Error("This is a static class");
}

SceneManager._scene = null;
SceneManager._nextScene = null;
SceneManager._stack = [];
SceneManager._exiting = false;
SceneManager._previousScene = null;
SceneManager._previousClass = null;
SceneManager._backgroundBitmap = null;
SceneManager._smoothDeltaTime = 1;
SceneManager._elapsedTime = 0;

SceneManager.run = function (sceneClass) {
  try {
    this.initialize();
    this.goto(sceneClass);
    Graphics.startGameLoop();
  } catch (e) {
    this.catchException(e);
  }
};

SceneManager.initialize = function () {
  this.checkBrowser();
  this.checkPluginErrors();
  this.initGraphics();
  this.initAudio();
  this.initVideo();
  this.initInput();
  this.setupEventHandlers();
};

SceneManager.checkBrowser = function () {
  if (!Utils.canUseWebGL()) {
    throw new Error("Your browser does not support WebGL.");
  }
  if (!Utils.canUseWebAudioAPI()) {
    throw new Error("Your browser does not support Web Audio API.");
  }
  if (!Utils.canUseCssFontLoading()) {
    throw new Error("Your browser does not support CSS Font Loading.");
  }
  if (!Utils.canUseIndexedDB()) {
    throw new Error("Your browser does not support IndexedDB.");
  }
};

SceneManager.checkPluginErrors = function () {
  PluginManager.checkErrors();
};

SceneManager.initGraphics = function () {
  if (!Graphics.initialize()) {
    throw new Error("Failed to initialize graphics.");
  }
  Graphics.setTickHandler(this.update.bind(this));
};

SceneManager.initAudio = function () {
  WebAudio.initialize();
};

SceneManager.initVideo = function () {
  Video.initialize(Graphics.width, Graphics.height);
};

SceneManager.initInput = function () {
  Input.initialize();
  TouchInput.initialize();
};

SceneManager.setupEventHandlers = function () {
  window.addEventListener("error", this.onError.bind(this));
  window.addEventListener("unhandledrejection", this.onReject.bind(this));
  window.addEventListener("unload", this.onUnload.bind(this));
  document.addEventListener("keydown", this.onKeyDown.bind(this));
};

SceneManager.update = function (deltaTime) {
  try {
    const n = this.determineRepeatNumber(deltaTime);
    for (let i = 0; i < n; i++) {
      this.updateMain();
    }
  } catch (e) {
    this.catchException(e);
  }
};

SceneManager.determineRepeatNumber = function (deltaTime) {
  // [Note] We consider environments where the refresh rate is higher than
  //   60Hz, but ignore sudden irregular deltaTime.
  this._smoothDeltaTime *= 0.8;
  this._smoothDeltaTime += Math.min(deltaTime, 2) * 0.2;
  if (this._smoothDeltaTime >= 0.9) {
    this._elapsedTime = 0;
    return Math.round(this._smoothDeltaTime);
  } else {
    this._elapsedTime += deltaTime;
    if (this._elapsedTime >= 1) {
      this._elapsedTime -= 1;
      return 1;
    }
    return 0;
  }
};

SceneManager.terminate = function () {
  if (Utils.isNwjs()) {
    nw.App.quit();
  }
};

SceneManager.onError = function (event) {
  console.error(event.message);
  console.error(event.filename, event.lineno);
  try {
    this.stop();
    Graphics.printError("Error", event.message, event);
    AudioManager.stopAll();
  } catch (e) {
    //
  }
};

SceneManager.onReject = function (event) {
  // Catch uncaught exception in Promise
  event.message = event.reason;
  this.onError(event);
};

SceneManager.onUnload = function () {
  ImageManager.clear();
  EffectManager.clear();
  AudioManager.stopAll();
};

SceneManager.onKeyDown = function (event) {
  if (!event.ctrlKey && !event.altKey) {
    switch (event.keyCode) {
      case 116: // F5
        this.reloadGame();
        break;
      case 119: // F8
        this.showDevTools();
        break;
    }
  }
};

SceneManager.reloadGame = function () {
  if (Utils.isNwjs()) {
    chrome.runtime.reload();
  }
};

SceneManager.showDevTools = function () {
  if (Utils.isNwjs() && Utils.isOptionValid("test")) {
    nw.Window.get().showDevTools();
  }
};

SceneManager.catchException = function (e) {
  if (e instanceof Error) {
    this.catchNormalError(e);
  } else if (e instanceof Array && e[0] === "LoadError") {
    this.catchLoadError(e);
  } else {
    this.catchUnknownError(e);
  }
  this.stop();
};

SceneManager.catchNormalError = function (e) {
  Graphics.printError(e.name, e.message, e);
  AudioManager.stopAll();
  console.error(e.stack);
};

SceneManager.catchLoadError = function (e) {
  const url = e[1];
  const retry = e[2];
  Graphics.printError("Failed to load", url);
  if (retry) {
    Graphics.showRetryButton(() => {
      retry();
      SceneManager.resume();
    });
  } else {
    AudioManager.stopAll();
  }
};

SceneManager.catchUnknownError = function (e) {
  Graphics.printError("UnknownError", String(e));
  AudioManager.stopAll();
};

SceneManager.updateMain = function () {
  this.updateFrameCount();
  this.updateInputData();
  this.updateEffekseer();
  this.changeScene();
  this.updateScene();
};

SceneManager.updateFrameCount = function () {
  Graphics.frameCount++;
};

SceneManager.updateInputData = function () {
  Input.update();
  TouchInput.update();
};

SceneManager.updateEffekseer = function () {
  if (Graphics.effekseer && this.isGameActive()) {
    Graphics.effekseer.update();
  }
};

SceneManager.changeScene = function () {
  if (this.isSceneChanging() && !this.isCurrentSceneBusy()) {
    if (this._scene) {
      this._scene.terminate();
      this.onSceneTerminate();
    }
    this._scene = this._nextScene;
    this._nextScene = null;
    if (this._scene) {
      this._scene.create();
      this.onSceneCreate();
    }
    if (this._exiting) {
      this.terminate();
    }
  }
};

SceneManager.updateScene = function () {
  if (this._scene) {
    if (this._scene.isStarted()) {
      if (this.isGameActive()) {
        this._scene.update();
      }
    } else if (this._scene.isReady()) {
      this.onBeforeSceneStart();
      this._scene.start();
      this.onSceneStart();
    }
  }
};

SceneManager.isGameActive = function () {
  // [Note] We use "window.top" to support an iframe.
  try {
    return window.top.document.hasFocus();
  } catch (e) {
    // SecurityError
    return true;
  }
};

SceneManager.onSceneTerminate = function () {
  this._previousScene = this._scene;
  this._previousClass = this._scene.constructor;
  Graphics.setStage(null);
};

SceneManager.onSceneCreate = function () {
  Graphics.startLoading();
};

SceneManager.onBeforeSceneStart = function () {
  if (this._previousScene) {
    this._previousScene.destroy();
    this._previousScene = null;
  }
  if (Graphics.effekseer) {
    Graphics.effekseer.stopAll();
  }
};

SceneManager.onSceneStart = function () {
  Graphics.endLoading();
  Graphics.setStage(this._scene);
};

SceneManager.isSceneChanging = function () {
  return this._exiting || !!this._nextScene;
};

SceneManager.isCurrentSceneBusy = function () {
  return this._scene && this._scene.isBusy();
};

SceneManager.isNextScene = function (sceneClass) {
  return this._nextScene && this._nextScene.constructor === sceneClass;
};

SceneManager.isPreviousScene = function (sceneClass) {
  return this._previousClass === sceneClass;
};

SceneManager.goto = function (sceneClass) {
  if (sceneClass) {
    this._nextScene = new sceneClass();
  }
  if (this._scene) {
    this._scene.stop();
  }
};

SceneManager.push = function (sceneClass) {
  this._stack.push(this._scene.constructor);
  this.goto(sceneClass);
};

SceneManager.pop = function () {
  if (this._stack.length > 0) {
    this.goto(this._stack.pop());
  } else {
    this.exit();
  }
};

SceneManager.exit = function () {
  this.goto(null);
  this._exiting = true;
};

SceneManager.clearStack = function () {
  this._stack = [];
};

SceneManager.stop = function () {
  Graphics.stopGameLoop();
};

SceneManager.prepareNextScene = function () {
  this._nextScene.prepare(...arguments);
};

SceneManager.snap = function () {
  return Bitmap.snap(this._scene);
};

SceneManager.snapForBackground = function () {
  if (this._backgroundBitmap) {
    this._backgroundBitmap.destroy();
  }
  this._backgroundBitmap = this.snap();
};

SceneManager.backgroundBitmap = function () {
  return this._backgroundBitmap;
};

SceneManager.resume = function () {
  TouchInput.update();
  Graphics.startGameLoop();
};

//-----------------------------------------------------------------------------
// BattleManager
//
// The static class that manages battle progress.

function BattleManager() {
  throw new Error("This is a static class");
}

BattleManager.setup = function (troopId, canEscape, canLose) {
  this.initMembers();
  this._canEscape = canEscape;
  this._canLose = canLose;
  $gameTroop.setup(troopId);
  $gameScreen.onBattleStart();
  this.makeEscapeRatio();
};

BattleManager.initMembers = function () {
  this._phase = "";
  this._inputting = false;
  this._canEscape = false;
  this._canLose = false;
  this._battleTest = false;
  this._eventCallback = null;
  this._preemptive = false;
  this._surprise = false;
  this._currentActor = null;
  this._actionForcedBattler = null;
  this._mapBgm = null;
  this._mapBgs = null;
  this._actionBattlers = [];
  this._subject = null;
  this._action = null;
  this._targets = [];
  this._logWindow = null;
  this._spriteset = null;
  this._escapeRatio = 0;
  this._escaped = false;
  this._rewards = {};
  this._tpbNeedsPartyCommand = true;
};

BattleManager.isTpb = function () {
  return $dataSystem.battleSystem >= 1;
};

BattleManager.isActiveTpb = function () {
  return $dataSystem.battleSystem === 1;
};

BattleManager.isBattleTest = function () {
  return this._battleTest;
};

BattleManager.setBattleTest = function (battleTest) {
  this._battleTest = battleTest;
};

BattleManager.setEventCallback = function (callback) {
  this._eventCallback = callback;
};

BattleManager.setLogWindow = function (logWindow) {
  this._logWindow = logWindow;
};

BattleManager.setSpriteset = function (spriteset) {
  this._spriteset = spriteset;
};

BattleManager.onEncounter = function () {
  this._preemptive = Math.random() < this.ratePreemptive();
  this._surprise = Math.random() < this.rateSurprise() && !this._preemptive;
};

BattleManager.ratePreemptive = function () {
  return $gameParty.ratePreemptive($gameTroop.agility());
};

BattleManager.rateSurprise = function () {
  return $gameParty.rateSurprise($gameTroop.agility());
};

BattleManager.saveBgmAndBgs = function () {
  this._mapBgm = AudioManager.saveBgm();
  this._mapBgs = AudioManager.saveBgs();
};

BattleManager.playBattleBgm = function () {
  AudioManager.playBgm($gameSystem.battleBgm());
  AudioManager.stopBgs();
};

BattleManager.playVictoryMe = function () {
  AudioManager.playMe($gameSystem.victoryMe());
};

BattleManager.playDefeatMe = function () {
  AudioManager.playMe($gameSystem.defeatMe());
};

BattleManager.replayBgmAndBgs = function () {
  if (this._mapBgm) {
    AudioManager.replayBgm(this._mapBgm);
  } else {
    AudioManager.stopBgm();
  }
  if (this._mapBgs) {
    AudioManager.replayBgs(this._mapBgs);
  }
};

BattleManager.makeEscapeRatio = function () {
  this._escapeRatio = (0.5 * $gameParty.agility()) / $gameTroop.agility();
};

BattleManager.update = function (timeActive) {
  if (!this.isBusy() && !this.updateEvent()) {
    this.updatePhase(timeActive);
  }
  if (this.isTpb()) {
    this.updateTpbInput();
  }
};

BattleManager.updatePhase = function (timeActive) {
  switch (this._phase) {
    case "start":
      this.updateStart();
      break;
    case "turn":
      this.updateTurn(timeActive);
      break;
    case "action":
      this.updateAction();
      break;
    case "turnEnd":
      this.updateTurnEnd();
      break;
    case "battleEnd":
      this.updateBattleEnd();
      break;
  }
};

BattleManager.updateEvent = function () {
  switch (this._phase) {
    case "start":
    case "turn":
    case "turnEnd":
      if (this.isActionForced()) {
        this.processForcedAction();
        return true;
      } else {
        return this.updateEventMain();
      }
  }
  return this.checkAbort();
};

BattleManager.updateEventMain = function () {
  $gameTroop.updateInterpreter();
  $gameParty.requestMotionRefresh();
  if ($gameTroop.isEventRunning() || this.checkBattleEnd()) {
    return true;
  }
  $gameTroop.setupBattleEvent();
  if ($gameTroop.isEventRunning() || SceneManager.isSceneChanging()) {
    return true;
  }
  return false;
};

BattleManager.isBusy = function () {
  return (
    $gameMessage.isBusy() ||
    this._spriteset.isBusy() ||
    this._logWindow.isBusy()
  );
};

BattleManager.updateTpbInput = function () {
  if (this._inputting) {
    this.checkTpbInputClose();
  } else {
    this.checkTpbInputOpen();
  }
};

BattleManager.checkTpbInputClose = function () {
  if (!this.isPartyTpbInputtable() || this.needsActorInputCancel()) {
    this.cancelActorInput();
    this._currentActor = null;
    this._inputting = false;
  }
};

BattleManager.checkTpbInputOpen = function () {
  if (this.isPartyTpbInputtable()) {
    if (this._tpbNeedsPartyCommand) {
      this._inputting = true;
      this._tpbNeedsPartyCommand = false;
    } else {
      this.selectNextCommand();
    }
  }
};

BattleManager.isPartyTpbInputtable = function () {
  return $gameParty.canInput() && this.isTpbMainPhase();
};

BattleManager.needsActorInputCancel = function () {
  return this._currentActor && !this._currentActor.canInput();
};

BattleManager.isTpbMainPhase = function () {
  return ["turn", "turnEnd", "action"].includes(this._phase);
};

BattleManager.isInputting = function () {
  return this._inputting;
};

BattleManager.isInTurn = function () {
  return this._phase === "turn";
};

BattleManager.isTurnEnd = function () {
  return this._phase === "turnEnd";
};

BattleManager.isAborting = function () {
  return this._phase === "aborting";
};

BattleManager.isBattleEnd = function () {
  return this._phase === "battleEnd";
};

BattleManager.canEscape = function () {
  return this._canEscape;
};

BattleManager.canLose = function () {
  return this._canLose;
};

BattleManager.isEscaped = function () {
  return this._escaped;
};

BattleManager.actor = function () {
  return this._currentActor;
};

BattleManager.startBattle = function () {
  this._phase = "start";
  $gameSystem.onBattleStart();
  $gameParty.onBattleStart(this._preemptive);
  $gameTroop.onBattleStart(this._surprise);
  this.displayStartMessages();
};

BattleManager.displayStartMessages = function () {
  for (const name of $gameTroop.enemyNames()) {
    $gameMessage.add(TextManager.emerge.format(name));
  }
  if (this._preemptive) {
    $gameMessage.add(TextManager.preemptive.format($gameParty.name()));
  } else if (this._surprise) {
    $gameMessage.add(TextManager.surprise.format($gameParty.name()));
  }
};

BattleManager.startInput = function () {
  this._phase = "input";
  this._inputting = true;
  $gameParty.makeActions();
  $gameTroop.makeActions();
  this._currentActor = null;
  if (this._surprise || !$gameParty.canInput()) {
    this.startTurn();
  }
};

BattleManager.inputtingAction = function () {
  return this._currentActor ? this._currentActor.inputtingAction() : null;
};

BattleManager.selectNextCommand = function () {
  if (this._currentActor) {
    if (this._currentActor.selectNextCommand()) {
      return;
    }
    this.finishActorInput();
  }
  this.selectNextActor();
};

BattleManager.selectNextActor = function () {
  this.changeCurrentActor(true);
  if (!this._currentActor) {
    if (this.isTpb()) {
      this.changeCurrentActor(true);
    } else {
      this.startTurn();
    }
  }
};

BattleManager.selectPreviousCommand = function () {
  if (this._currentActor) {
    if (this._currentActor.selectPreviousCommand()) {
      return;
    }
    this.cancelActorInput();
  }
  this.selectPreviousActor();
};

BattleManager.selectPreviousActor = function () {
  if (this.isTpb()) {
    this.changeCurrentActor(true);
    if (!this._currentActor) {
      this._inputting = $gameParty.canInput();
    }
  } else {
    this.changeCurrentActor(false);
  }
};

BattleManager.changeCurrentActor = function (forward) {
  const members = $gameParty.battleMembers();
  let actor = this._currentActor;
  for (;;) {
    const currentIndex = members.indexOf(actor);
    actor = members[currentIndex + (forward ? 1 : -1)];
    if (!actor || actor.canInput()) {
      break;
    }
  }
  this._currentActor = actor ? actor : null;
  this.startActorInput();
};

BattleManager.startActorInput = function () {
  if (this._currentActor) {
    this._currentActor.setActionState("inputting");
    this._inputting = true;
  }
};

BattleManager.finishActorInput = function () {
  if (this._currentActor) {
    if (this.isTpb()) {
      this._currentActor.startTpbCasting();
    }
    this._currentActor.setActionState("waiting");
  }
};

BattleManager.cancelActorInput = function () {
  if (this._currentActor) {
    this._currentActor.setActionState("undecided");
  }
};

BattleManager.updateStart = function () {
  if (this.isTpb()) {
    this._phase = "turn";
  } else {
    this.startInput();
  }
};

BattleManager.startTurn = function () {
  this._phase = "turn";
  $gameTroop.increaseTurn();
  $gameParty.requestMotionRefresh();
  if (!this.isTpb()) {
    this.makeActionOrders();
    this._logWindow.startTurn();
    this._inputting = false;
  }
};

BattleManager.updateTurn = function (timeActive) {
  $gameParty.requestMotionRefresh();
  if (this.isTpb() && timeActive) {
    this.updateTpb();
  }
  if (!this._subject) {
    this._subject = this.getNextSubject();
  }
  if (this._subject) {
    this.processTurn();
  } else if (!this.isTpb()) {
    this.endTurn();
  }
};

BattleManager.updateTpb = function () {
  $gameParty.updateTpb();
  $gameTroop.updateTpb();
  this.updateAllTpbBattlers();
  this.checkTpbTurnEnd();
};

BattleManager.updateAllTpbBattlers = function () {
  for (const battler of this.allBattleMembers()) {
    this.updateTpbBattler(battler);
  }
};

BattleManager.updateTpbBattler = function (battler) {
  if (battler.isTpbTurnEnd()) {
    battler.onTurnEnd();
    battler.startTpbTurn();
    this.displayBattlerStatus(battler, false);
  } else if (battler.isTpbReady()) {
    battler.startTpbAction();
    this._actionBattlers.push(battler);
  } else if (battler.isTpbTimeout()) {
    battler.onTpbTimeout();
    this.displayBattlerStatus(battler, true);
  }
};

BattleManager.checkTpbTurnEnd = function () {
  if ($gameTroop.isTpbTurnEnd()) {
    this.endTurn();
  }
};

BattleManager.processTurn = function () {
  const subject = this._subject;
  const action = subject.currentAction();
  if (action) {
    action.prepare();
    if (action.isValid()) {
      this.startAction();
    }
    subject.removeCurrentAction();
  } else {
    this.endAction();
    this._subject = null;
  }
};

BattleManager.endBattlerActions = function (battler) {
  battler.setActionState(this.isTpb() ? "undecided" : "done");
  battler.onAllActionsEnd();
  battler.clearTpbChargeTime();
  this.displayBattlerStatus(battler, true);
};

BattleManager.endTurn = function () {
  this._phase = "turnEnd";
  this._preemptive = false;
  this._surprise = false;
};

BattleManager.updateTurnEnd = function () {
  if (this.isTpb()) {
    this.startTurn();
  } else {
    this.endAllBattlersTurn();
    this._phase = "start";
  }
};

BattleManager.endAllBattlersTurn = function () {
  for (const battler of this.allBattleMembers()) {
    battler.onTurnEnd();
    this.displayBattlerStatus(battler, false);
  }
};

BattleManager.displayBattlerStatus = function (battler, current) {
  this._logWindow.displayAutoAffectedStatus(battler);
  if (current) {
    this._logWindow.displayCurrentState(battler);
  }
  this._logWindow.displayRegeneration(battler);
};

BattleManager.getNextSubject = function () {
  for (;;) {
    const battler = this._actionBattlers.shift();
    if (!battler) {
      return null;
    }
    if (battler.isBattleMember() && battler.isAlive()) {
      return battler;
    }
  }
};

BattleManager.allBattleMembers = function () {
  return $gameParty.battleMembers().concat($gameTroop.members());
};

BattleManager.makeActionOrders = function () {
  const battlers = [];
  if (!this._surprise) {
    battlers.push(...$gameParty.battleMembers());
  }
  if (!this._preemptive) {
    battlers.push(...$gameTroop.members());
  }
  for (const battler of battlers) {
    battler.makeSpeed();
  }
  battlers.sort((a, b) => b.speed() - a.speed());
  this._actionBattlers = battlers;
};

BattleManager.startAction = function () {
  const subject = this._subject;
  const action = subject.currentAction();
  const targets = action.makeTargets();
  this._phase = "action";
  this._action = action;
  this._targets = targets;
  subject.cancelMotionRefresh();
  subject.useItem(action.item());
  this._action.applyGlobal();
  this._logWindow.startAction(subject, action, targets);
};

BattleManager.updateAction = function () {
  const target = this._targets.shift();
  if (target) {
    this.invokeAction(this._subject, target);
  } else {
    this.endAction();
  }
};

BattleManager.endAction = function () {
  this._logWindow.endAction(this._subject);
  this._phase = "turn";
  if (this._subject.numActions() === 0) {
    this.endBattlerActions(this._subject);
    this._subject = null;
  }
};

BattleManager.invokeAction = function (subject, target) {
  this._logWindow.push("pushBaseLine");
  if (Math.random() < this._action.itemCnt(target)) {
    this.invokeCounterAttack(subject, target);
  } else if (Math.random() < this._action.itemMrf(target)) {
    this.invokeMagicReflection(subject, target);
  } else {
    this.invokeNormalAction(subject, target);
  }
  subject.setLastTarget(target);
  this._logWindow.push("popBaseLine");
};

BattleManager.invokeNormalAction = function (subject, target) {
  const realTarget = this.applySubstitute(target);
  this._action.apply(realTarget);
  this._logWindow.displayActionResults(subject, realTarget);
};

BattleManager.invokeCounterAttack = function (subject, target) {
  const action = new Game_Action(target);
  action.setAttack();
  action.apply(subject);
  this._logWindow.displayCounter(target);
  this._logWindow.displayActionResults(target, subject);
};

BattleManager.invokeMagicReflection = function (subject, target) {
  this._action._reflectionTarget = target;
  this._logWindow.displayReflection(target);
  this._action.apply(subject);
  this._logWindow.displayActionResults(target, subject);
};

BattleManager.applySubstitute = function (target) {
  if (this.checkSubstitute(target)) {
    const substitute = target.friendsUnit().substituteBattler();
    if (substitute && target !== substitute) {
      this._logWindow.displaySubstitute(substitute, target);
      return substitute;
    }
  }
  return target;
};

BattleManager.checkSubstitute = function (target) {
  return target.isDying() && !this._action.isCertainHit();
};

BattleManager.isActionForced = function () {
  return !!this._actionForcedBattler;
};

BattleManager.forceAction = function (battler) {
  this._actionForcedBattler = battler;
  this._actionBattlers.remove(battler);
};

BattleManager.processForcedAction = function () {
  if (this._actionForcedBattler) {
    if (this._subject) {
      this.endBattlerActions(this._subject);
    }
    this._subject = this._actionForcedBattler;
    this._actionForcedBattler = null;
    this.startAction();
    this._subject.removeCurrentAction();
  }
};

BattleManager.abort = function () {
  this._phase = "aborting";
};

BattleManager.checkBattleEnd = function () {
  if (this._phase) {
    if ($gameParty.isEscaped()) {
      this.processPartyEscape();
      return true;
    } else if ($gameParty.isAllDead()) {
      this.processDefeat();
      return true;
    } else if ($gameTroop.isAllDead()) {
      this.processVictory();
      return true;
    }
  }
  return false;
};

BattleManager.checkAbort = function () {
  if (this.isAborting()) {
    this.processAbort();
    return true;
  }
  return false;
};

BattleManager.processVictory = function () {
  $gameParty.removeBattleStates();
  $gameParty.performVictory();
  this.playVictoryMe();
  this.replayBgmAndBgs();
  this.makeRewards();
  this.displayVictoryMessage();
  this.displayRewards();
  this.gainRewards();
  this.endBattle(0);
};

BattleManager.processEscape = function () {
  $gameParty.performEscape();
  SoundManager.playEscape();
  const success = this._preemptive || Math.random() < this._escapeRatio;
  if (success) {
    this.onEscapeSuccess();
  } else {
    this.onEscapeFailure();
  }
  return success;
};

BattleManager.onEscapeSuccess = function () {
  this.displayEscapeSuccessMessage();
  this._escaped = true;
  this.processAbort();
};

BattleManager.onEscapeFailure = function () {
  $gameParty.onEscapeFailure();
  this.displayEscapeFailureMessage();
  this._escapeRatio += 0.1;
  if (!this.isTpb()) {
    this.startTurn();
  }
};

BattleManager.processPartyEscape = function () {
  this._escaped = true;
  this.processAbort();
};

BattleManager.processAbort = function () {
  $gameParty.removeBattleStates();
  this._logWindow.clear();
  this.replayBgmAndBgs();
  this.endBattle(1);
};

BattleManager.processDefeat = function () {
  this.displayDefeatMessage();
  this.playDefeatMe();
  if (this._canLose) {
    this.replayBgmAndBgs();
  } else {
    AudioManager.stopBgm();
  }
  this.endBattle(2);
};

BattleManager.endBattle = function (result) {
  this._phase = "battleEnd";
  this.cancelActorInput();
  this._inputting = false;
  if (this._eventCallback) {
    this._eventCallback(result);
  }
  if (result === 0) {
    $gameSystem.onBattleWin();
  } else if (this._escaped) {
    $gameSystem.onBattleEscape();
  }
  $gameTemp.clearCommonEventReservation();
};

BattleManager.updateBattleEnd = function () {
  if (this.isBattleTest()) {
    AudioManager.stopBgm();
    SceneManager.exit();
  } else if (!this._escaped && $gameParty.isAllDead()) {
    if (this._canLose) {
      $gameParty.reviveBattleMembers();
      SceneManager.pop();
    } else {
      SceneManager.goto(Scene_Gameover);
    }
  } else {
    SceneManager.pop();
  }
  this._phase = "";
};

BattleManager.makeRewards = function () {
  this._rewards = {
    gold: $gameTroop.goldTotal(),
    exp: $gameTroop.expTotal(),
    items: $gameTroop.makeDropItems(),
  };
};

BattleManager.displayVictoryMessage = function () {
  $gameMessage.add(TextManager.victory.format($gameParty.name()));
};

BattleManager.displayDefeatMessage = function () {
  $gameMessage.add(TextManager.defeat.format($gameParty.name()));
};

BattleManager.displayEscapeSuccessMessage = function () {
  $gameMessage.add(TextManager.escapeStart.format($gameParty.name()));
};

BattleManager.displayEscapeFailureMessage = function () {
  $gameMessage.add(TextManager.escapeStart.format($gameParty.name()));
  $gameMessage.add("\\." + TextManager.escapeFailure);
};

BattleManager.displayRewards = function () {
  this.displayExp();
  this.displayGold();
  this.displayDropItems();
};

BattleManager.displayExp = function () {
  const exp = this._rewards.exp;
  if (exp > 0) {
    const text = TextManager.obtainExp.format(exp, TextManager.exp);
    $gameMessage.add("\\." + text);
  }
};

BattleManager.displayGold = function () {
  const gold = this._rewards.gold;
  if (gold > 0) {
    $gameMessage.add("\\." + TextManager.obtainGold.format(gold));
  }
};

BattleManager.displayDropItems = function () {
  const items = this._rewards.items;
  if (items.length > 0) {
    $gameMessage.newPage();
    for (const item of items) {
      $gameMessage.add(TextManager.obtainItem.format(item.name));
    }
  }
};

BattleManager.gainRewards = function () {
  this.gainExp();
  this.gainGold();
  this.gainDropItems();
};

BattleManager.gainExp = function () {
  const exp = this._rewards.exp;
  for (const actor of $gameParty.allMembers()) {
    actor.gainExp(exp);
  }
};

BattleManager.gainGold = function () {
  $gameParty.gainGold(this._rewards.gold);
};

BattleManager.gainDropItems = function () {
  const items = this._rewards.items;
  for (const item of items) {
    $gameParty.gainItem(item, 1);
  }
};

//-----------------------------------------------------------------------------
// PluginManager
//
// The static class that manages the plugins.

function PluginManager() {
  throw new Error("This is a static class");
}

PluginManager._scripts = [];
PluginManager._errorUrls = [];
PluginManager._parameters = {};
PluginManager._commands = {};

PluginManager.setup = function (plugins) {
  for (const plugin of plugins) {
    const pluginName = Utils.extractFileName(plugin.name);
    if (plugin.status && !this._scripts.includes(pluginName)) {
      this.setParameters(pluginName, plugin.parameters);
      this.loadScript(plugin.name);
      this._scripts.push(pluginName);
    }
  }
};

PluginManager.parameters = function (name) {
  return this._parameters[name.toLowerCase()] || {};
};

PluginManager.setParameters = function (name, parameters) {
  this._parameters[name.toLowerCase()] = parameters;
};

PluginManager.loadScript = function (filename) {
  const url = this.makeUrl(filename);
  const script = document.createElement("script");
  script.type = "text/javascript";
  script.src = url;
  script.async = false;
  script.defer = true;
  script.onerror = this.onError.bind(this);
  script._url = url;
  document.body.appendChild(script);
};

PluginManager.onError = function (e) {
  this._errorUrls.push(e.target._url);
};

PluginManager.makeUrl = function (filename) {
  return "js/plugins/" + Utils.encodeURI(filename) + ".js";
};

PluginManager.checkErrors = function () {
  const url = this._errorUrls.shift();
  if (url) {
    this.throwLoadError(url);
  }
};

PluginManager.throwLoadError = function (url) {
  throw new Error("Failed to load: " + url);
};

PluginManager.registerCommand = function (pluginName, commandName, func) {
  const key = pluginName + ":" + commandName;
  this._commands[key] = func;
};

PluginManager.callCommand = function (self, pluginName, commandName, args) {
  const key = pluginName + ":" + commandName;
  const func = this._commands[key];
  if (typeof func === "function") {
    func.bind(self)(args);
  }
};

//-----------------------------------------------------------------------------

repo_name: dazed/translations

license: unknown