UNPKG

yoni-mcscripts-lib

Version:

为 Minecraft Script API 中的部分接口创建了 wrapper,并提供简单的事件管理器和任务管理器,另附有一些便于代码编写的一些小工具。

623 lines (600 loc) 20.3 kB
import { deepcopy } from "../lib/deepcopy.js"; export class Config { #configMap: ConfigMap = Config.createConfigMapObject(); /** * 根据指定的键获取一个 Config 对象。 * @param key - 配置键。 * @returns Config 对象或 null(如果未找到键)。 * @throws 如果配置键链接到的值不是 Config 对象,抛出 `TypeError`。 */ getConfig(key: string): Config | null { const value = this.get(key, undefined); if (value === undefined) return null; if (value instanceof Config || value === null) return value; else throw new TypeError("the value that key linked doesn't a config map"); } createConfig(key: string): Config { this.setConfig(key, Config.createConfigMapObject()) return this.getConfig(key) as Config; } /** * 根据指定的键获取布尔值。 * @param key - 配置键。 * @param defaultValue - 如果未找到键时的默认值。默认值为 false。 * @returns 键的布尔值。 */ getBoolean(key: string, defaultValue: boolean = false): boolean { const value = this.get(key, undefined); if (value === undefined){ return defaultValue; } else { return (!!value); } } /** * 根据指定的键获取 bigint 值。 * @param key - 配置键。 * @param defaultValue - 如果未找到键时的默认值。默认值为 0。 * @returns 键的 bigint 值。 * @throws 如果不支持 bigint,则抛出 SyntaxError。 */ getBigInt(key: string, defaultValue?: bigint): bigint { if (!BigInt) throw new SyntaxError("bigint not support"); const value = this.get(key, undefined); if (value === undefined){ return defaultValue ?? BigInt(0); } if (typeof value === "bigint"){ return value; } else { try { return BigInt(String(value)); } catch { return BigInt(0); } } } /** * 根据指定的键获取整数值。 * @param key - 配置键。 * @param defaultValue - 如果未找到键时的默认值。默认值为 NaN。 * @returns 键的整数值。 */ getInt(key: string, defaultValue: number = NaN): number { const value = this.get(key, undefined); if (value === undefined){ return defaultValue; } else { return parseInt(String(value)); } } /** * 根据指定的键获取浮点数值。 * @param key - 配置键。 * @param defaultValue - 如果未找到键时的默认值。默认值为 NaN。 * @returns 键的浮点数值。 */ getFloat(key: string, defaultValue: number = NaN): number { const value = this.get(key, undefined); if (value === undefined){ return defaultValue; } else { return parseFloat(String(value)); } } /** * 根据指定的键获取数字值。 * @param key - 配置键。 * @param defaultValue - 如果未找到键时的默认值。默认值为 NaN。 * @returns 键的数字值。 */ getNumber(key: string, defaultValue: number = NaN): number { const value = this.get(key, undefined); if (value === undefined){ return defaultValue; } else { return Number(value); } } /** * 根据指定的键获取字符串值。 * @param key - 配置键。 * @param defaultValue - 如果未找到键时的默认值。默认值为空字符串。 * @returns 键的字符串值。 */ getString(key: string, defaultValue: string = ""): string { const value = this.get(key, undefined); if (value === undefined){ return defaultValue; } else { return String(value); } } /** * 根据指定的键获取值。 * @param key - 配置键。 * @returns 键的值。 */ get(key: string): ConfigValue; /** * 根据指定的键获取值。 * @param key - 配置键。 * @param defaultValue - 在指定的配置名没有配置时返回的默认值。 * @returns 键的值。 */ get(key: string, defaultValue: any): ConfigValue | typeof defaultValue; get(key: string, defaultValue?: any){ let map = this.#configMap; let value = null; for (const oneKey of Config.getConfigKeys(key)){ if (typeof map === "object" && oneKey in map) value = map[oneKey]; else return arguments.length >= 2 ? defaultValue : null; map = value as ConfigMap; } if (typeof value === "object" && value !== null) return Config.createFromConfigMap(value as ConfigMap); else return value; } /** * 设置指定键的 Config 对象。 * @param key - 配置键。 * @param config - 要设置的 Config 对象。 * @throws 如果提供的值不是 Config 对象,则抛出错误。 */ setConfig(key: string, config: Config | ConfigMap){ if (config instanceof Config) this.set(key, config); else if (typeof config === "object" && Config.isConfigMap(config)) this.#configMap[key] = config; else throw new TypeError("not a config"); } /** * 设置指定键的布尔值。 * @param key - 配置键。 * @param value - 要设置的布尔值。 */ setBoolean(key: string, value: boolean){ this.set(key, !!value); } /** * 设置指定键的 bigint 值。 * @param key - 配置键。 * @param value - 要设置的 bigint 值。 * @throws 如果不支持 bigint,则抛出 SyntaxError。 * @throws 如果提供的值不是 bigint,则抛出 TypeError。 */ setBigInt(key: string, value: bigint){ if (!BigInt) throw new SyntaxError("bigint not support"); if (typeof value !== "bigint") throw new TypeError("not a BigInt"); this.set(key, value); } /** * 设置指定键的整数值。 * @param key - 配置键。 * @param value - 要设置的整数值。 */ setInt(key: string, value: number){ this.set(key, parseInt(String(value))); } /** * 设置指定键的浮点数值。 * @param key - 配置键。 * @param value - 要设置的浮点数值。 */ setFloat(key: string, value: number){ this.set(key, parseFloat(String(value))); } /** * 设置指定键的数字值。 * @param key - 配置键。 * @param value - 要设置的数字值。 */ setNumber(key: string, value: number){ if (typeof value === "bigint") value = Number((value as unknown as bigint).toString()); else value = Number(value); this.set(key, value); } /** * 设置指定键的字符串值。 * @param key - 配置键。 * @param value - 要设置的整数值。 */ setString(key: string, value: string){ this.set(key, String(value)); } /** * 设置指定键的值。 * @param key - 配置键。 * @param value - 要设置的值。 * @returns 配置的旧值。 */ set(key: string, value: ConfigValue): ConfigValue { let lastVal; if (value instanceof Config){ lastVal = this.#set(key, value.#configMap); } else { switch (typeof value){ case "string": case "boolean": case "bigint": case "number": break; case "undefined": value = null; break; case "object": if (value === null) break; default: throw new TypeError("unsupported value type: "+typeof value); } lastVal = this.#set(key, value); } let returnVal: ConfigValue; if (typeof lastVal === "object" && lastVal !== null) returnVal = Config.createFromConfigMap(lastVal); else returnVal = lastVal; (async function emit(co: Config){ co.#onConfigChangedCallbacks.slice(0).forEach(async function onConfigChanged(cb){ cb(key, value, returnVal, co); }); })(this); return returnVal; } #set(key: string, value: PlainValue | ConfigMap | undefined): PlainValue | ConfigMap { let map = this.#configMap; const keys: string[] = Config.getConfigKeys(key); while (keys.length > 0){ const oneKey = keys.shift() as string; if (keys.length === 0){ const lastVal = oneKey in map ? map[oneKey] : null; if (value === undefined) delete map[oneKey]; else map[oneKey] = value; return lastVal; } if (map[oneKey] === undefined || map[oneKey] === null){ map[oneKey] = Config.createConfigMapObject(); } if (typeof map[oneKey] !== "object"){ throw new Error("不允许间接覆盖一个非空值"); } map = map[oneKey] as ConfigMap; } // 正常情况下不可能执行到这里 return null; } /** * 设置指定键的 Config 对象。 * @param key - 配置键。 * @param config - 要设置的 Config 对象。 * @returns 返回调用对象自身以允许链式调用。 * @throws 如果提供的值不是 Config 对象,则抛出错误。 */ addConfig(key: string, config: Config | ConfigMap): Config { this.setConfig(key, config); return this; } /** * 设置指定键的布尔值。 * @param key - 配置键。 * @param value - 要设置的布尔值。 * @returns 返回调用对象自身以允许链式调用。 */ addBoolean(key: string, value: boolean): Config { this.setBoolean(key, value); return this; } /** * 设置指定键的 bigint 值。 * @param key - 配置键。 * @param value - 要设置的 bigint 值。 * @returns 返回调用对象自身以允许链式调用。 * @throws 如果不支持 bigint,则抛出 SyntaxError。 * @throws 如果提供的值不是 bigint,则抛出 TypeError。 */ addBigInt(key: string, value: bigint): Config { this.setBigInt(key, value); return this; } /** * 设置指定键的整数值。 * @param key - 配置键。 * @param value - 要设置的整数值。 * @returns 返回调用对象自身以允许链式调用。 */ addInt(key: string, value: number): Config { this.setInt(key, value); return this; } /** * 设置指定键的浮点数值。 * @param key - 配置键。 * @param value - 要设置的浮点数值。 * @returns 返回调用对象自身以允许链式调用。 */ addFloat(key: string, value: number): Config { this.setFloat(key, value); return this; } /** * 设置指定键的数字值。 * @param key - 配置键。 * @param value - 要设置的数字值。 * @returns 返回调用对象自身以允许链式调用。 */ addNumber(key: string, value: number): Config { this.setNumber(key, value); return this; } /** * 设置指定键的字符串值。 * @param key - 配置键。 * @param value - 要设置的整数值。 * @returns 返回调用对象自身以允许链式调用。 */ addString(key: string, value: string): Config { this.setString(key, value); return this; } /** * 设置指定键的值。 * @param key - 配置键。 * @param value - 要设置的值。 * @returns 返回调用对象自身以允许链式调用。 */ add(key: string, value: ConfigValue): Config { this.set(key, value); return this; } /** * 检查指定的配置键是否为一个对象。 * @param key - 配置键。 */ hasConfig(key: string): boolean { const value = this.get(key); return typeof value === "object" && value !== null; } /** * 检查指定的配置键是否为一个布尔值。 * @param key - 配置键。 */ hasBoolean(key: string): boolean { return typeof this.get(key) === "boolean"; } /** * 检查指定的配置键是否为一个 bigint 值。 * @param key - 配置键。 */ hasBigInt(key: string): boolean { return typeof this.get(key) === "bigint"; } /** * 检查指定的配置键是否为一个整数值。 * @param key - 配置键。 */ hasInt(key: string): boolean { const value = this.get(key); return typeof value === "number" && parseInt(String(value)) === value; } /** * 检查指定的配置键是否为一个浮点数值。 * @param key - 配置键。 */ hasFloat(key: string): boolean { const value = this.get(key); return typeof value === "number" && parseFloat(String(value)) === value; } /** * 检查指定的配置键是否为一个数字值(这包括 `NaN`、`Infinity` 以及它们的负数形式)。 * @param key - 配置键。 */ hasNumber(key: string): boolean { return typeof this.get(key) === "number"; } /** * 检查指定的配置键是否为一个字符串值。 * @param key - 配置键。 */ hasString(key: string): boolean { return typeof this.get(key) === "string"; } /** * 检查指定的配置键是否已经设置为某个值。 * @param key - 配置键。 */ has(key: string): boolean { return this.get(key, undefined) !== undefined; } /** * 检查指定的配置键是否已经设置为 `null`。 * @param key - 配置键。 */ isNull(key: string): boolean { return this.get(key, undefined) === null; } /** * 从 ConfigMap 中删除配置键。 * @returns key - 指定配置键的值是否被移除。 */ delete(key: string): boolean { if (this.has(key)){ this.#set(key, undefined); return !(this.has(key)); } return false; } /** * 从 JSON 反序列化得到其对象后,使用此对象作为 ConfigMap 创建新的 Config 对象。 * @param configJSON - 配置JSON。 */ constructor(configJSON: string) /** * 创建新的 Config 对象,并读取 configMap 中的键值设定到新创建的对象中。 * @param configMap - 配置。 */ constructor(configMap: Map<string, ConfigValue>) constructor() constructor(config?: Map<string, ConfigValue> | string){ if (!config) return; if (typeof config === "string"){ deepcopy(JSON.parse(config), { rootObject: this.#configMap, newObjectMaker: Config.createConfigMapObject, copyFunction: Config.#configValueCopyFunction, }); } else { for (const kv of config.entries()){ this.set(kv[0], kv[1]); } } } /** * 克隆此 Config。 * @returns 新的 Config 对象。 */ clone(){ return new Config(this.toJSON()); } toJSON(){ return JSON.stringify(this.#configMap); } #onConfigChangedCallbacks: ConfigChangeCallbackFunc[] = []; onceConfigChanged(cb: ConfigChangeCallbackFunc): typeof cb { const autoRemove = () => { this.offConfigChanged(autoRemove, cb); }; this.onConfigChanged(autoRemove); this.onConfigChanged(cb); return cb; } onConfigChanged(cb: ConfigChangeCallbackFunc): typeof cb { this.#onConfigChangedCallbacks.push(cb); return cb; } offConfigChanged(...cbs: ConfigChangeCallbackFunc[]): void { this.#onConfigChangedCallbacks = this.#onConfigChangedCallbacks.filter(icb => !cbs.includes(icb)); } static createConfigMapObject(): ConfigMap { const object = Object.create(null); Config.#configObjects.add(object); return object; } static #configObjects = new WeakSet<ConfigMap>(); /** * 创建新的 Config 对象,并从keyvaluemap中读取值,写入到新创建的Config对象当中。 * @param configMap - 配置存储对象。 * @returns 新创建的 Config 对象。 */ static createFromMap(config: Map<string, ConfigValue>){ return new Config(config); } /** * 创建新的 Config 对象,并将configMap作为新创建的Cinfig对象的存储。 * @param configMap - 配置映射对象。 * @returns 新创建的 Config 对象。 */ static createFromConfigMap(configMap: ConfigMap){ if (Config.isConfigMap(configMap)){ const config = new Config(); config.#configMap = configMap; return config; } throw new TypeError("unexpected config object"); } /** * 创建新的 Config对象,并从object中获取配置的值后写入新创建的Config对象当中。 */ static createFromObject(object: ConfigMap){ const config = new Config(); deepcopy(object, { rootObject: config.#configMap, newObjectMaker: Config.createConfigMapObject, copyFunction: Config.#configValueCopyFunction, }); return config; } static #configValueCopyFunction = { isCopyableValue(value: any){ switch (typeof value){ case "string": case "boolean": case "bigint": case "number": case "undefined": break; case "object": if (value === null) break; default: return false; } return true; }, getCopiedValue(value: any){ switch (typeof value){ case "string": case "boolean": case "bigint": case "number": break; case "undefined": value = null; break; case "object": if (value === null) break; default: throw new TypeError("value not copyable"); } return value; } } /** * 读取配置键名为一个数组。 * @param key - 配置键。 * @returns 配置的键数组。 */ static getConfigKeys(key: string){ //@ts-ignore 只是觉得很有趣 let chain = String.prototype.split.call(key, "."); if (chain.includes("")) throw new Error("invalid key"); return chain; } static isConfigMap(v: any): v is ConfigMap { return Config.#configObjects.has(v); } } type ConfigChangeCallbackFunc = (key: string, newVal: ConfigValue, lastVal: ConfigValue, config: Config) => void; /** * 表示一个配置映射对象。 */ export interface ConfigMap { [key: string]: ConfigMap | PlainValue; } export type PlainValue = string | number | bigint | boolean | null; /** * 配置值的类型。 */ export type ConfigValue = Config | PlainValue; //文档注释由ChatGPT生成,毕竟写这东西挺费劲的