orbit/js/config/Config.js

56 lines
1.6 KiB
JavaScript

/**
* Global config singleton.
*
* Initialized exactly once in main.js from the JSON files in /data,
* then any module in the game can import it and read values:
*
* import { config } from '../config/Config.js';
* const thrust = config.get('ship.thrust', 900);
* const menu = config.section('menu', {});
*
* Conventions (see docs/PROJECT_NOTES.md):
* - tunable values live in data/*.json, never hardcoded in code;
* - add a new data file by listing it in data/manifest.json.
*/
class Config {
constructor() {
this.data = {};
}
/** @param {Record<string, any>} data keyed by config file name (no .json) */
init(data) {
this.data = data ?? {};
return this;
}
/** @returns {boolean} true if a top-level section (a file in /data) exists */
has(name) {
return Object.prototype.hasOwnProperty.call(this.data, name);
}
/**
* @returns {object} the value at `name` if it's an object — dotted
* paths allowed (`section('systems.types')` works) — else `fallback`.
*/
section(name, fallback = {}) {
const value = this.get(name, undefined);
return (value && typeof value === 'object') ? value : fallback;
}
/**
* Dotted-path getter: config.get('menu.buttons.newGame.label', 'New Game')
* @returns {*} the value at `path`, or `fallback` if any segment is missing
*/
get(path, fallback = undefined) {
if (!path) return fallback;
let node = this.data;
for (const part of String(path).split('.')) {
if (node == null || typeof node !== 'object') return fallback;
node = node[part];
}
return node === undefined ? fallback : node;
}
}
export const config = new Config();