56 lines
1.6 KiB
JavaScript
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();
|