Source: DataManager.js

DataManager.js

//-----------------------------------------------------------------------------
// DataManager
//
// The static class that manages the database and game objects.
/**
 * The static class that manages the database and game objects.
 *
 * @namespace
 */
function DataManager() {
    throw new Error("This is a static class");
}
/** 
 * Array used to store the database version of the actors
 * 
 * @global
 */
$dataActors = null;
/** 
 * Array used to store the database version of the classes
 * 
 * @global
 */
$dataClasses = null;
/** 
 * Array used to store the database version of the skills
 * 
 * @global
 */
$dataSkills = null;
/** 
 * Array used to store the database version of the items
 * 
 * @global
 */
$dataItems = null;
/** 
 * Array used to store the database version of the weapons
 * 
 * @global
 */
$dataWeapons = null;
/** 
 * Array used to store the database version of the armors
 * 
 * @global
 */
$dataArmors = null;
/** 
 * Array used to store the database version of the enemies
 * 
 * @global
 */
$dataEnemies = null;
/** 
 * Array used to store the database version of the troops
 * 
 * @global
 */
$dataTroops = null;
/** 
 * Array used to store the database version of the states
 * 
 * @global
 */
$dataStates = null;
/** 
 * Array used to store the database version of the animations
 * 
 * @global
 */
$dataAnimations = null;
/** 
 * Array used to store the database version of the tilesets
 * 
 * @global
 */
$dataTilesets = null;
/** 
 * Array used to store the database version of the common events
 * 
 * @global
 */
$dataCommonEvents = null;
/** 
 * Array used to store the database version of the system tabs
 * 
 * @global
 */
$dataSystem = null;
/** 
 * Stores the map infos
 * 
 * @global
 */
$dataMapInfos = null;
/** 
 * Stores data about a map
 * 
 * @global
 */
$dataMap = null;
/** 
 * Global reference to Game_Temp
 * 
 * @global
 */
$gameTemp = null;
/** 
 * Global reference to Game_System
 * 
 * @global
 */
$gameSystem = null;
/** 
 * Global reference to Game_Screen
 * 
 * @global
 */
$gameScreen = null;
/** 
 * Global reference to Game_Timer
 * 
 * @global
 */
$gameTimer = null;
/** 
 * Global reference to Game_Message
 * 
 * @global
 */
$gameMessage = null;
/** 
 * Global reference to Game_Switches
 * 
 * @global
 */
$gameSwitches = null;
/** 
 * Global reference to Game_Variables
 * 
 * @global
 */
$gameVariables = null;
/** 
 * Global reference to Game_SelfSwitches
 * 
 * @global
 */
$gameSelfSwitches = null;
/** 
 * Global reference to Game_Actors
 * 
 * @global
 */
$gameActors = null;
/** 
 * Global reference to Game_Party
 * 
 * @global
 */
$gameParty = null;
/** 
 * Global reference to Game_Troop
 * 
 * @global
 */
$gameTroop = null;
/** 
 * Global reference to Game_Map
 * 
 * @global
 */
$gameMap = null;
/** 
 * Global reference to Game_Player
 * 
 * @global
 */
$gamePlayer = null;
/** 
 * Global reference set when testing an event
 * 
 * @global
 */
$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" }
];

/**
 * Loads the "global" object
 *
 * @static
 */
DataManager.loadGlobalInfo = function() {
    StorageManager.loadObject("global")
        .then(globalInfo => {
            this._globalInfo = globalInfo;
            this.removeInvalidGlobalInfo();
            return 0;
        })
        .catch(() => {
            this._globalInfo = [];
        });
};

/**
 * Check for and remove invalid global info if any exists
 *
 * @static
 */
DataManager.removeInvalidGlobalInfo = function() {
    const globalInfo = this._globalInfo;
    for (const info of globalInfo) {
        const savefileId = globalInfo.indexOf(info);
        if (!this.savefileExists(savefileId)) {
            delete globalInfo[savefileId];
        }
    }
};

/**
 * Saves the "global" object
 *
 * @static
 */
DataManager.saveGlobalInfo = function() {
    StorageManager.saveObject("global", this._globalInfo);
};

/**
 * Check if global info is loaded
 *
 * @static
 * @return {boolean} True if global info is loaded.
 */
DataManager.isGlobalInfoLoaded = function() {
    return !!this._globalInfo;
};

/**
 * Loads each of the database files
 *
 * @static
 */
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");
    }
};

/**
 * Loads the database file with the given name and src
 *
 * @static
 * @param {string} name - The name of the data object to store loaded data in
 * @param {string} src - The path to the data file to load
 */
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();
};

/**
 * Handling for when the xhr object is loaded
 *
 * @static
 * @param {XMLHttpRequest} xhr - The XMLHttpRequest object
 * @param {string} name - The name of the data object to store loaded data in
 * @param {string} src - The path to the data file to load
 * @param {string} url - The full path to the data file to load
 */
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);
    }
};

/**
 * Handling for when the xhr object encounters an error
 *
 * @static
 * @param {string} name - The name of the data object to store loaded data in
 * @param {string} src - The path to the data file to load
 * @param {string} url - The full path to the data file to load
 */
DataManager.onXhrError = function(name, src, url) {
    const error = { name: name, src: src, url: url };
    this._errors.push(error);
};

/**
 * Check if all of the database files are loaded
 *
 * @static
 * @return {boolean} True if all database files are loaded
 */
DataManager.isDatabaseLoaded = function() {
    this.checkError();
    for (const databaseFile of this._databaseFiles) {
        if (!window[databaseFile.name]) {
            return false;
        }
    }
    return true;
};

/**
 * Loads a map data file by the map's id
 *
 * @static
 * @param {number} mapId - The ID of the map to load
 */
DataManager.loadMapData = function(mapId) {
    if (mapId > 0) {
        const filename = "Map%1.json".format(mapId.padZero(3));
        this.loadDataFile("$dataMap", filename);
    } else {
        this.makeEmptyMap();
    }
};

/**
 * Makes an empty map with no data or events, stored in $dataMap
 *
 * @static
 */
DataManager.makeEmptyMap = function() {
    $dataMap = {};
    $dataMap.data = [];
    $dataMap.events = [];
    $dataMap.width = 100;
    $dataMap.height = 100;
    $dataMap.scrollType = 3;
};

/**
 * Check if the map is loaded
 *
 * @static
 * @return {boolean} True if map data is loaded
 */
DataManager.isMapLoaded = function() {
    this.checkError();
    return !!$dataMap;
};

/**
 * Handling for when a data object is loaded
 *
 * @static
 * @param {*} object - The object that was loaded
 */
DataManager.onLoad = function(object) {
    if (this.isMapObject(object)) {
        this.extractMetadata(object);
        this.extractArrayMetadata(object.events);
    } else {
        this.extractArrayMetadata(object);
    }
};

/**
 * Check if an object represents map data
 *
 * @static
 * @param {*} object - The object to check for map info
 * @return {boolean} True if the object represents a map
 */
DataManager.isMapObject = function(object) {
    return !!(object.data && object.events);
};

/**
 * Extract metadata from an array object
 *
 * @static
 * @param {Array} array - The array to extract metadata from
 */
DataManager.extractArrayMetadata = function(array) {
    if (Array.isArray(array)) {
        for (const data of array) {
            if (data && "note" in data) {
                this.extractMetadata(data);
            }
        }
    }
};

/**
 * Extract metadata from an individual object
 *
 * @static
 * @param {*} data - The data that might have a meta property in its note
 */
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;
        }
    }
};

/**
 * Check if there are any errors, throw a retry screen error if any exist
 *
 * @static
 * @throws Retry screen error
 */
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];
    }
};

/**
 * Check if the game is in battle test mode
 *
 * @static
 * @return {boolean} True if the game is in battle test mode
 */
DataManager.isBattleTest = function() {
    return Utils.isOptionValid("btest");
};

/**
 * Check if the game is in event test mode
 *
 * @static
 * @return {boolean} True if the game is in event test mode
 */
DataManager.isEventTest = function() {
    return Utils.isOptionValid("etest");
};

/**
 * Check if title skip option is enabled
 *
 * @static
 * @since Version 1.7.0
 * @return {boolean} True if title skip is on
 */
DataManager.isTitleSkip = function() {
    return Utils.isOptionValid("tskip");
};

/**
 * Check if the given object is a skill from the database
 *
 * @static
 * @param {*} item - Item to check for inclusion in $dataSkills
 * @return {boolean} True if the object is a skill
 */
DataManager.isSkill = function(item) {
    return item && $dataSkills.includes(item);
};

/**
 * Check if the given object is an item from the database
 *
 * @static
 * @param {*} item - Item to check for inclusion in $dataItems
 * @return {boolean} True if the object is an item
 */
DataManager.isItem = function(item) {
    return item && $dataItems.includes(item);
};

/**
 * Check if the given object is a weapon from the database
 *
 * @static
 * @param {*} item - Item to check for inclusion in $dataWeapons
 * @return {boolean} True if the object is a weapon
 */
DataManager.isWeapon = function(item) {
    return item && $dataWeapons.includes(item);
};

/**
 * Check if the given object is an armor from the database
 *
 * @static
 * @param {*} item - Item to check for inclusion in $dataArmors
 * @return {boolean} True if the object is an armor
 */
DataManager.isArmor = function(item) {
    return item && $dataArmors.includes(item);
};

/**
 * Creates $gameTemp and the other game objects
 *
 * @static
 */
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();
};

/**
 * Handles setting up a new game
 *
 * @static
 */
DataManager.setupNewGame = function() {
    this.createGameObjects();
    this.selectSavefileForNewGame();
    $gameParty.setupStartingMembers();
    $gamePlayer.setupForNewGame();
    Graphics.frameCount = 0;
};

/**
 * Handles setting up a battle test
 *
 * @static
 */
DataManager.setupBattleTest = function() {
    this.createGameObjects();
    $gameParty.setupBattleTest();
    BattleManager.setup($dataSystem.testTroopId, true, false);
    BattleManager.setBattleTest(true);
    BattleManager.playBattleBgm();
};

/**
 * Handles setting up an event test
 *
 * @static
 */
DataManager.setupEventTest = function() {
    this.createGameObjects();
    this.selectSavefileForNewGame();
    $gameParty.setupStartingMembers();
    $gamePlayer.reserveTransfer(-1, 8, 6);
    $gamePlayer.setTransparent(false);
};

/**
 * Check if there are any save files
 *
 * @static
 * @return {boolean} True if any save files exist
 */
DataManager.isAnySavefileExists = function() {
    return this._globalInfo.some(x => x);
};

/**
 * Gets the last used save file
 *
 * @static
 * @return {number} The index of the last save file
 */
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;
};

/**
 * Gets the earliest save file
 *
 * @static
 * @return {number} The index of the earliest save file
 */
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;
};

/**
 * Gets the index of an empty save file
 *
 * @static
 * @return {number} The index of the empty save file
 */
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;
    }
};

/**
 * Loads the images needed for display in the file select screen
 *
 * @static
 */
DataManager.loadAllSavefileImages = function() {
    for (const info of this._globalInfo.filter(x => x)) {
        this.loadSavefileImages(info);
    }
};

/**
 * Loads the images needed for display in the file select screen from a specific save file
 *
 * @static
 * @param {*} info - The save file info with image data to load
 */
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]);
        }
    }
};

/**
 * Determines the maximum number of save files allowed
 *
 * @static
 * @return {number} The maximum number of save files allowed
 */
DataManager.maxSavefiles = function() {
    return 20;
};

/**
 * Gets the save file info for a specific save file id
 *
 * @static
 * @param {number} savefileId - The id of the save file to load
 * @return {Object|null} Either the save file info if exists, or null
 */
DataManager.savefileInfo = function(savefileId) {
    const globalInfo = this._globalInfo;
    return globalInfo[savefileId] ? globalInfo[savefileId] : null;
};

/**
 * Checks if a given save file exists by save file id
 *
 * @static
 * @param {number} savefileId - The id of the save file to check
 * @return {boolean} True if save data exists for that file id
 */
DataManager.savefileExists = function(savefileId) {
    const saveName = this.makeSavename(savefileId);
    return StorageManager.exists(saveName);
};

/**
 * Tries to save the game in the given save file id slot
 *
 * @static
 * @param {number} savefileId - The id of the save file slot to save
 */
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;
    });
};

/**
 * Tries to load game info from the given save file id slot
 *
 * @static
 * @param {number} savefileId - The id of the save file slot to load
 */
DataManager.loadGame = function(savefileId) {
    const saveName = this.makeSavename(savefileId);
    return StorageManager.loadObject(saveName).then(contents => {
        this.createGameObjects();
        this.extractSaveContents(contents);
        this.correctDataErrors();
        return 0;
    });
};

/**
 * Creates the file name of the save file from the save file id slot
 *
 * @static
 * @param {number} savefileId - The id of the save file slot
 * @return {string} The save file name
 */
DataManager.makeSavename = function(savefileId) {
    return "file%1".format(savefileId);
};

/**
 * Selects the initial save file slot for a new game
 *
 * @static
 */
DataManager.selectSavefileForNewGame = function() {
    const emptySavefileId = this.emptySavefileId();
    const earliestSavefileId = this.earliestSavefileId();
    if (emptySavefileId > 0) {
        $gameSystem.setSavefileId(emptySavefileId);
    } else {
        $gameSystem.setSavefileId(earliestSavefileId);
    }
};

/**
 * Creates save file info for the file select screen
 *
 * @static
 * @return {Object} The save file info contents
 */
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;
};

/**
 * Creates save file contents
 *
 * @static
 * @return {Object} The save file contents
 */
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;
};

/**
 * Extracts the content from a save file to the various game objects
 *
 * @static
 * @param {Object} contents - The save file 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;
};

/**
 * Tries to correct any bad data
 *
 * @static
 */
DataManager.correctDataErrors = function() {
    $gameParty.removeInvalidMembers();
};